@redocly/recheck 0.15.0 → 2.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1034) hide show
  1. package/README.md +71 -1886
  2. package/examples/appendices/google.appendix.yaml +1 -1
  3. package/examples/appendices/inclusive-language.appendix.yaml +1 -1
  4. package/examples/appendices/microsoft.appendix.yaml +1 -1
  5. package/examples/appendices/plain-language.appendix.yaml +1 -1
  6. package/examples/appendices/technical-english.appendix.yaml +1 -1
  7. package/examples/google.yaml +7 -7
  8. package/examples/inclusive-language.yaml +7 -7
  9. package/examples/microsoft.yaml +7 -7
  10. package/examples/plain-language.yaml +7 -7
  11. package/examples/technical-english.yaml +7 -7
  12. package/lib/actions/baseline.d.ts +15 -0
  13. package/lib/actions/baseline.d.ts.map +1 -0
  14. package/lib/actions/baseline.js +51 -0
  15. package/lib/actions/baseline.js.map +1 -0
  16. package/lib/actions/lint.d.ts +51 -0
  17. package/lib/actions/lint.d.ts.map +1 -0
  18. package/lib/actions/lint.js +123 -0
  19. package/lib/actions/lint.js.map +1 -0
  20. package/lib/actions/markdoc-schema.d.ts +34 -0
  21. package/lib/actions/markdoc-schema.d.ts.map +1 -0
  22. package/lib/actions/markdoc-schema.js +106 -0
  23. package/lib/actions/markdoc-schema.js.map +1 -0
  24. package/lib/actions/readability.d.ts +25 -0
  25. package/lib/actions/readability.d.ts.map +1 -0
  26. package/lib/actions/readability.js +47 -0
  27. package/lib/actions/readability.js.map +1 -0
  28. package/lib/actions/roots.d.ts +7 -0
  29. package/lib/actions/roots.d.ts.map +1 -0
  30. package/lib/actions/roots.js +36 -0
  31. package/lib/actions/roots.js.map +1 -0
  32. package/lib/config/presets/api-descriptions.d.ts +8 -0
  33. package/lib/config/presets/api-descriptions.d.ts.map +1 -0
  34. package/lib/config/presets/api-descriptions.js +24 -0
  35. package/lib/config/presets/api-descriptions.js.map +1 -0
  36. package/lib/config/presets/google.d.ts +3 -0
  37. package/lib/config/presets/google.d.ts.map +1 -0
  38. package/lib/config/presets/google.js +1086 -0
  39. package/lib/config/presets/google.js.map +1 -0
  40. package/lib/config/presets/inclusive-language.d.ts +3 -0
  41. package/lib/config/presets/inclusive-language.d.ts.map +1 -0
  42. package/lib/config/presets/inclusive-language.js +151 -0
  43. package/lib/config/presets/inclusive-language.js.map +1 -0
  44. package/lib/config/presets/index.d.ts +16 -0
  45. package/lib/config/presets/index.d.ts.map +1 -0
  46. package/lib/config/presets/index.js +38 -0
  47. package/lib/config/presets/index.js.map +1 -0
  48. package/lib/config/presets/markdoc.d.ts +3 -0
  49. package/lib/config/presets/markdoc.d.ts.map +1 -0
  50. package/lib/config/presets/markdoc.js +45 -0
  51. package/lib/config/presets/markdoc.js.map +1 -0
  52. package/lib/config/presets/markdown-relaxed.d.ts +3 -0
  53. package/lib/config/presets/markdown-relaxed.d.ts.map +1 -0
  54. package/lib/config/presets/markdown-relaxed.js +34 -0
  55. package/lib/config/presets/markdown-relaxed.js.map +1 -0
  56. package/lib/config/presets/markdown.d.ts +11 -0
  57. package/lib/config/presets/markdown.d.ts.map +1 -0
  58. package/lib/config/presets/markdown.js +73 -0
  59. package/lib/config/presets/markdown.js.map +1 -0
  60. package/lib/config/presets/microsoft.d.ts +3 -0
  61. package/lib/config/presets/microsoft.d.ts.map +1 -0
  62. package/lib/config/presets/microsoft.js +1260 -0
  63. package/lib/config/presets/microsoft.js.map +1 -0
  64. package/lib/config/presets/minimal.d.ts +3 -0
  65. package/lib/config/presets/minimal.d.ts.map +1 -0
  66. package/lib/config/presets/minimal.js +13 -0
  67. package/lib/config/presets/minimal.js.map +1 -0
  68. package/lib/config/presets/plain-language.d.ts +3 -0
  69. package/lib/config/presets/plain-language.d.ts.map +1 -0
  70. package/lib/config/presets/plain-language.js +175 -0
  71. package/lib/config/presets/plain-language.js.map +1 -0
  72. package/lib/config/presets/prose.d.ts +3 -0
  73. package/lib/config/presets/prose.d.ts.map +1 -0
  74. package/lib/config/presets/prose.js +60 -0
  75. package/lib/config/presets/prose.js.map +1 -0
  76. package/lib/config/presets/technical-english.d.ts +22 -0
  77. package/lib/config/presets/technical-english.d.ts.map +1 -0
  78. package/lib/config/presets/technical-english.js +61 -0
  79. package/lib/config/presets/technical-english.js.map +1 -0
  80. package/lib/config/resolve.d.ts +36 -0
  81. package/lib/config/resolve.d.ts.map +1 -0
  82. package/lib/config/resolve.js +106 -0
  83. package/lib/config/resolve.js.map +1 -0
  84. package/lib/config/schema.d.ts +215 -0
  85. package/lib/config/schema.d.ts.map +1 -0
  86. package/lib/config/schema.js +152 -0
  87. package/lib/config/schema.js.map +1 -0
  88. package/lib/config/validate.d.ts +19 -0
  89. package/lib/config/validate.d.ts.map +1 -0
  90. package/lib/config/validate.js +1049 -0
  91. package/lib/config/validate.js.map +1 -0
  92. package/lib/core/auto-fix.d.ts.map +1 -0
  93. package/lib/core/auto-fix.js +94 -0
  94. package/lib/core/auto-fix.js.map +1 -0
  95. package/lib/core/baseline.d.ts +43 -0
  96. package/lib/core/baseline.d.ts.map +1 -0
  97. package/lib/core/baseline.js +142 -0
  98. package/lib/core/baseline.js.map +1 -0
  99. package/lib/core/case-preserve.d.ts +11 -0
  100. package/lib/core/case-preserve.d.ts.map +1 -0
  101. package/lib/core/case-preserve.js +22 -0
  102. package/lib/core/case-preserve.js.map +1 -0
  103. package/lib/core/directives.d.ts.map +1 -0
  104. package/lib/core/directives.js +66 -0
  105. package/lib/core/directives.js.map +1 -0
  106. package/lib/core/files.d.ts +31 -0
  107. package/lib/core/files.d.ts.map +1 -0
  108. package/lib/core/files.js +184 -0
  109. package/lib/core/files.js.map +1 -0
  110. package/lib/core/inline-code.d.ts +33 -0
  111. package/lib/core/inline-code.d.ts.map +1 -0
  112. package/lib/core/inline-code.js +50 -0
  113. package/lib/core/inline-code.js.map +1 -0
  114. package/lib/core/line-endings.d.ts +13 -0
  115. package/lib/core/line-endings.d.ts.map +1 -0
  116. package/lib/core/line-endings.js +39 -0
  117. package/lib/core/line-endings.js.map +1 -0
  118. package/lib/core/markdoc-tags.d.ts +48 -0
  119. package/lib/core/markdoc-tags.d.ts.map +1 -0
  120. package/lib/core/markdoc-tags.js +102 -0
  121. package/lib/core/markdoc-tags.js.map +1 -0
  122. package/lib/core/prose-extract.d.ts +6 -0
  123. package/lib/core/prose-extract.d.ts.map +1 -0
  124. package/lib/core/prose-extract.js +71 -0
  125. package/lib/core/prose-extract.js.map +1 -0
  126. package/lib/core/readability.d.ts +17 -0
  127. package/lib/core/readability.d.ts.map +1 -0
  128. package/lib/core/readability.js +34 -0
  129. package/lib/core/readability.js.map +1 -0
  130. package/lib/core/rule-filters.d.ts +29 -0
  131. package/lib/core/rule-filters.d.ts.map +1 -0
  132. package/lib/core/rule-filters.js +88 -0
  133. package/lib/core/rule-filters.js.map +1 -0
  134. package/lib/core/runner.d.ts +60 -0
  135. package/lib/core/runner.d.ts.map +1 -0
  136. package/lib/core/runner.js +305 -0
  137. package/lib/core/runner.js.map +1 -0
  138. package/lib/core/summary.d.ts +4 -0
  139. package/lib/core/summary.d.ts.map +1 -0
  140. package/lib/core/summary.js +37 -0
  141. package/lib/core/summary.js.map +1 -0
  142. package/lib/core/timing.d.ts +9 -0
  143. package/lib/core/timing.d.ts.map +1 -0
  144. package/lib/core/timing.js +19 -0
  145. package/lib/core/timing.js.map +1 -0
  146. package/lib/data/markdoc-realm-schema.d.ts.map +1 -0
  147. package/lib/data/markdoc-realm-schema.js +782 -0
  148. package/lib/data/markdoc-realm-schema.js.map +1 -0
  149. package/lib/data/proper-nouns.d.ts.map +1 -0
  150. package/lib/data/proper-nouns.js +42 -0
  151. package/lib/data/proper-nouns.js.map +1 -0
  152. package/lib/data/realm-front-matter-schema.d.ts +12 -0
  153. package/lib/data/realm-front-matter-schema.d.ts.map +1 -0
  154. package/lib/data/realm-front-matter-schema.js +51 -0
  155. package/lib/data/realm-front-matter-schema.js.map +1 -0
  156. package/lib/index.d.ts +81 -0
  157. package/lib/index.d.ts.map +1 -0
  158. package/lib/index.js +114 -0
  159. package/lib/index.js.map +1 -0
  160. package/lib/metrics/formulas.d.ts +9 -0
  161. package/lib/metrics/formulas.d.ts.map +1 -0
  162. package/lib/metrics/formulas.js +59 -0
  163. package/lib/metrics/formulas.js.map +1 -0
  164. package/lib/metrics/statistics.d.ts +14 -0
  165. package/lib/metrics/statistics.d.ts.map +1 -0
  166. package/lib/metrics/statistics.js +35 -0
  167. package/lib/metrics/statistics.js.map +1 -0
  168. package/lib/parser/index.d.ts +13 -0
  169. package/lib/parser/index.d.ts.map +1 -0
  170. package/lib/parser/index.js +123 -0
  171. package/lib/parser/index.js.map +1 -0
  172. package/lib/parser/markdoc/extract-statics.d.ts +28 -0
  173. package/lib/parser/markdoc/extract-statics.d.ts.map +1 -0
  174. package/lib/parser/markdoc/extract-statics.js +80 -0
  175. package/lib/parser/markdoc/extract-statics.js.map +1 -0
  176. package/lib/parser/markdoc/pairing.d.ts +38 -0
  177. package/lib/parser/markdoc/pairing.d.ts.map +1 -0
  178. package/lib/parser/markdoc/pairing.js +77 -0
  179. package/lib/parser/markdoc/pairing.js.map +1 -0
  180. package/lib/parser/markdoc/schema.d.ts +56 -0
  181. package/lib/parser/markdoc/schema.d.ts.map +1 -0
  182. package/lib/parser/markdoc/schema.js +60 -0
  183. package/lib/parser/markdoc/schema.js.map +1 -0
  184. package/lib/parser/markdoc/span.d.ts +52 -0
  185. package/lib/parser/markdoc/span.d.ts.map +1 -0
  186. package/lib/parser/markdoc/span.js +577 -0
  187. package/lib/parser/markdoc/span.js.map +1 -0
  188. package/lib/parser/markdoc/structure.d.ts +17 -0
  189. package/lib/parser/markdoc/structure.d.ts.map +1 -0
  190. package/lib/parser/markdoc/structure.js +121 -0
  191. package/lib/parser/markdoc/structure.js.map +1 -0
  192. package/lib/parser/markdoc/syntax.d.ts +17 -0
  193. package/lib/parser/markdoc/syntax.d.ts.map +1 -0
  194. package/lib/parser/markdoc/syntax.js +262 -0
  195. package/lib/parser/markdoc/syntax.js.map +1 -0
  196. package/lib/parser/types.d.ts.map +1 -0
  197. package/lib/presets.d.ts +6 -0
  198. package/lib/presets.d.ts.map +1 -0
  199. package/lib/presets.js +12 -0
  200. package/lib/presets.js.map +1 -0
  201. package/lib/rules/registry.d.ts +13 -0
  202. package/lib/rules/registry.d.ts.map +1 -0
  203. package/lib/rules/registry.js +54 -0
  204. package/lib/rules/registry.js.map +1 -0
  205. package/lib/rules/scope/capitalization.d.ts.map +1 -0
  206. package/lib/rules/scope/capitalization.js +128 -0
  207. package/lib/rules/scope/capitalization.js.map +1 -0
  208. package/lib/rules/scope/conditional.d.ts.map +1 -0
  209. package/lib/rules/scope/conditional.js +93 -0
  210. package/lib/rules/scope/conditional.js.map +1 -0
  211. package/lib/rules/scope/consistency.d.ts.map +1 -0
  212. package/lib/rules/scope/consistency.js +118 -0
  213. package/lib/rules/scope/consistency.js.map +1 -0
  214. package/lib/rules/scope/length.d.ts.map +1 -0
  215. package/lib/rules/scope/length.js +50 -0
  216. package/lib/rules/scope/length.js.map +1 -0
  217. package/lib/rules/scope/max-image-size.d.ts.map +1 -0
  218. package/lib/rules/scope/max-image-size.js +55 -0
  219. package/lib/rules/scope/max-image-size.js.map +1 -0
  220. package/lib/rules/scope/metric.d.ts +3 -0
  221. package/lib/rules/scope/metric.d.ts.map +1 -0
  222. package/lib/rules/scope/metric.js +59 -0
  223. package/lib/rules/scope/metric.js.map +1 -0
  224. package/lib/rules/scope/occurrence.d.ts.map +1 -0
  225. package/lib/rules/scope/occurrence.js +41 -0
  226. package/lib/rules/scope/occurrence.js.map +1 -0
  227. package/lib/rules/scope/pattern.d.ts.map +1 -0
  228. package/lib/rules/scope/pattern.js +58 -0
  229. package/lib/rules/scope/pattern.js.map +1 -0
  230. package/lib/rules/scope/repetition.d.ts.map +1 -0
  231. package/lib/rules/scope/repetition.js +134 -0
  232. package/lib/rules/scope/repetition.js.map +1 -0
  233. package/lib/rules/scope/semantic-line-breaks.d.ts.map +1 -0
  234. package/lib/rules/scope/semantic-line-breaks.js +180 -0
  235. package/lib/rules/scope/semantic-line-breaks.js.map +1 -0
  236. package/lib/rules/scope/spelling.d.ts +11 -0
  237. package/lib/rules/scope/spelling.d.ts.map +1 -0
  238. package/lib/rules/scope/spelling.js +146 -0
  239. package/lib/rules/scope/spelling.js.map +1 -0
  240. package/lib/rules/scope/swap.d.ts.map +1 -0
  241. package/lib/rules/scope/swap.js +111 -0
  242. package/lib/rules/scope/swap.js.map +1 -0
  243. package/lib/rules/scope/title-case.d.ts +24 -0
  244. package/lib/rules/scope/title-case.d.ts.map +1 -0
  245. package/lib/rules/scope/title-case.js +216 -0
  246. package/lib/rules/scope/title-case.js.map +1 -0
  247. package/lib/rules/token/blanks-around-fences.d.ts.map +1 -0
  248. package/lib/rules/token/blanks-around-fences.js.map +1 -0
  249. package/lib/rules/token/blanks-around-headings.d.ts.map +1 -0
  250. package/lib/rules/token/blanks-around-headings.js +96 -0
  251. package/lib/rules/token/blanks-around-headings.js.map +1 -0
  252. package/lib/rules/token/blanks-around-lists.d.ts.map +1 -0
  253. package/lib/rules/token/blanks-around-lists.js +48 -0
  254. package/lib/rules/token/blanks-around-lists.js.map +1 -0
  255. package/lib/rules/token/blanks-around-tables.d.ts.map +1 -0
  256. package/lib/rules/token/blanks-around-tables.js +40 -0
  257. package/lib/rules/token/blanks-around-tables.js.map +1 -0
  258. package/lib/rules/token/code-block-style.d.ts.map +1 -0
  259. package/lib/rules/token/code-block-style.js.map +1 -0
  260. package/lib/rules/token/code-fence-style.d.ts.map +1 -0
  261. package/lib/rules/token/code-fence-style.js.map +1 -0
  262. package/lib/rules/token/commands-show-output.d.ts.map +1 -0
  263. package/lib/rules/token/commands-show-output.js.map +1 -0
  264. package/lib/rules/token/descriptive-link-text.d.ts.map +1 -0
  265. package/lib/rules/token/descriptive-link-text.js +46 -0
  266. package/lib/rules/token/descriptive-link-text.js.map +1 -0
  267. package/lib/rules/token/fenced-code-language.d.ts.map +1 -0
  268. package/lib/rules/token/fenced-code-language.js.map +1 -0
  269. package/lib/rules/token/first-line-h1.d.ts.map +1 -0
  270. package/lib/rules/token/first-line-h1.js +83 -0
  271. package/lib/rules/token/first-line-h1.js.map +1 -0
  272. package/lib/rules/token/front-matter.d.ts.map +1 -0
  273. package/lib/rules/token/front-matter.js +169 -0
  274. package/lib/rules/token/front-matter.js.map +1 -0
  275. package/lib/rules/token/heading-increment.d.ts.map +1 -0
  276. package/lib/rules/token/heading-increment.js +26 -0
  277. package/lib/rules/token/heading-increment.js.map +1 -0
  278. package/lib/rules/token/heading-start-left.d.ts.map +1 -0
  279. package/lib/rules/token/heading-start-left.js.map +1 -0
  280. package/lib/rules/token/heading-style.d.ts.map +1 -0
  281. package/lib/rules/token/heading-style.js.map +1 -0
  282. package/lib/rules/token/helpers.d.ts +115 -0
  283. package/lib/rules/token/helpers.d.ts.map +1 -0
  284. package/lib/rules/token/helpers.js +502 -0
  285. package/lib/rules/token/helpers.js.map +1 -0
  286. package/lib/rules/token/hr-style.d.ts.map +1 -0
  287. package/lib/rules/token/hr-style.js.map +1 -0
  288. package/lib/rules/token/index.d.ts +65 -0
  289. package/lib/rules/token/index.d.ts.map +1 -0
  290. package/lib/rules/token/index.js +157 -0
  291. package/lib/rules/token/index.js.map +1 -0
  292. package/lib/rules/token/line-length.d.ts.map +1 -0
  293. package/lib/rules/token/line-length.js +96 -0
  294. package/lib/rules/token/line-length.js.map +1 -0
  295. package/lib/rules/token/link-fragments.d.ts.map +1 -0
  296. package/lib/rules/token/link-fragments.js +398 -0
  297. package/lib/rules/token/link-fragments.js.map +1 -0
  298. package/lib/rules/token/link-image-reference-definitions.d.ts.map +1 -0
  299. package/lib/rules/token/link-image-reference-definitions.js.map +1 -0
  300. package/lib/rules/token/link-image-style.d.ts.map +1 -0
  301. package/lib/rules/token/link-image-style.js +138 -0
  302. package/lib/rules/token/link-image-style.js.map +1 -0
  303. package/lib/rules/token/list-indent.d.ts.map +1 -0
  304. package/lib/rules/token/list-indent.js.map +1 -0
  305. package/lib/rules/token/list-length.d.ts.map +1 -0
  306. package/lib/rules/token/list-length.js +31 -0
  307. package/lib/rules/token/list-length.js.map +1 -0
  308. package/lib/rules/token/list-marker-space.d.ts.map +1 -0
  309. package/lib/rules/token/list-marker-space.js.map +1 -0
  310. package/lib/rules/token/markdoc-attributes.d.ts.map +1 -0
  311. package/lib/rules/token/markdoc-attributes.js +198 -0
  312. package/lib/rules/token/markdoc-attributes.js.map +1 -0
  313. package/lib/rules/token/markdoc-pairing.d.ts.map +1 -0
  314. package/lib/rules/token/markdoc-pairing.js +65 -0
  315. package/lib/rules/token/markdoc-pairing.js.map +1 -0
  316. package/lib/rules/token/markdoc-syntax.d.ts.map +1 -0
  317. package/lib/rules/token/markdoc-syntax.js +84 -0
  318. package/lib/rules/token/markdoc-syntax.js.map +1 -0
  319. package/lib/rules/token/markdoc-unknown-tag.d.ts.map +1 -0
  320. package/lib/rules/token/markdoc-unknown-tag.js +44 -0
  321. package/lib/rules/token/markdoc-unknown-tag.js.map +1 -0
  322. package/lib/rules/token/no-alt-text.d.ts.map +1 -0
  323. package/lib/rules/token/no-alt-text.js +43 -0
  324. package/lib/rules/token/no-alt-text.js.map +1 -0
  325. package/lib/rules/token/no-bare-urls.d.ts.map +1 -0
  326. package/lib/rules/token/no-bare-urls.js +74 -0
  327. package/lib/rules/token/no-bare-urls.js.map +1 -0
  328. package/lib/rules/token/no-blanks-blockquote.d.ts.map +1 -0
  329. package/lib/rules/token/no-blanks-blockquote.js.map +1 -0
  330. package/lib/rules/token/no-duplicate-heading.d.ts.map +1 -0
  331. package/lib/rules/token/no-duplicate-heading.js +89 -0
  332. package/lib/rules/token/no-duplicate-heading.js.map +1 -0
  333. package/lib/rules/token/no-duplicate-link-destinations.d.ts.map +1 -0
  334. package/lib/rules/token/no-duplicate-link-destinations.js +54 -0
  335. package/lib/rules/token/no-duplicate-link-destinations.js.map +1 -0
  336. package/lib/rules/token/no-emphasis-as-heading.d.ts.map +1 -0
  337. package/lib/rules/token/no-emphasis-as-heading.js +39 -0
  338. package/lib/rules/token/no-emphasis-as-heading.js.map +1 -0
  339. package/lib/rules/token/no-empty-headings.d.ts.map +1 -0
  340. package/lib/rules/token/no-empty-headings.js +21 -0
  341. package/lib/rules/token/no-empty-headings.js.map +1 -0
  342. package/lib/rules/token/no-empty-links.d.ts.map +1 -0
  343. package/lib/rules/token/no-empty-links.js.map +1 -0
  344. package/lib/rules/token/no-hard-tabs.d.ts.map +1 -0
  345. package/lib/rules/token/no-hard-tabs.js.map +1 -0
  346. package/lib/rules/token/no-inline-html.d.ts.map +1 -0
  347. package/lib/rules/token/no-inline-html.js +41 -0
  348. package/lib/rules/token/no-inline-html.js.map +1 -0
  349. package/lib/rules/token/no-multiple-blanks.d.ts.map +1 -0
  350. package/lib/rules/token/no-multiple-blanks.js.map +1 -0
  351. package/lib/rules/token/no-multiple-space-atx.d.ts +9 -0
  352. package/lib/rules/token/no-multiple-space-atx.d.ts.map +1 -0
  353. package/lib/rules/token/no-multiple-space-atx.js +46 -0
  354. package/lib/rules/token/no-multiple-space-atx.js.map +1 -0
  355. package/lib/rules/token/no-multiple-space-closed-atx.d.ts.map +1 -0
  356. package/lib/rules/token/no-multiple-space-closed-atx.js.map +1 -0
  357. package/lib/rules/token/no-reversed-links.d.ts.map +1 -0
  358. package/lib/rules/token/no-reversed-links.js.map +1 -0
  359. package/lib/rules/token/no-space-in-code.d.ts.map +1 -0
  360. package/lib/rules/token/no-space-in-code.js.map +1 -0
  361. package/lib/rules/token/no-space-in-emphasis.d.ts.map +1 -0
  362. package/lib/rules/token/no-space-in-emphasis.js +72 -0
  363. package/lib/rules/token/no-space-in-emphasis.js.map +1 -0
  364. package/lib/rules/token/no-space-in-links.d.ts.map +1 -0
  365. package/lib/rules/token/no-space-in-links.js.map +1 -0
  366. package/lib/rules/token/no-trailing-punctuation.d.ts.map +1 -0
  367. package/lib/rules/token/no-trailing-punctuation.js.map +1 -0
  368. package/lib/rules/token/no-trailing-spaces.d.ts.map +1 -0
  369. package/lib/rules/token/no-trailing-spaces.js.map +1 -0
  370. package/lib/rules/token/ol-prefix.d.ts.map +1 -0
  371. package/lib/rules/token/ol-prefix.js.map +1 -0
  372. package/lib/rules/token/proper-names.d.ts.map +1 -0
  373. package/lib/rules/token/proper-names.js +87 -0
  374. package/lib/rules/token/proper-names.js.map +1 -0
  375. package/lib/rules/token/reference-links-images.d.ts.map +1 -0
  376. package/lib/rules/token/reference-links-images.js.map +1 -0
  377. package/lib/rules/token/required-headings.d.ts.map +1 -0
  378. package/lib/rules/token/required-headings.js +73 -0
  379. package/lib/rules/token/required-headings.js.map +1 -0
  380. package/lib/rules/token/single-h1.d.ts.map +1 -0
  381. package/lib/rules/token/single-h1.js +43 -0
  382. package/lib/rules/token/single-h1.js.map +1 -0
  383. package/lib/rules/token/table-column-count.d.ts.map +1 -0
  384. package/lib/rules/token/table-column-count.js.map +1 -0
  385. package/lib/rules/token/table-column-style.d.ts.map +1 -0
  386. package/lib/rules/token/table-column-style.js +162 -0
  387. package/lib/rules/token/table-column-style.js.map +1 -0
  388. package/lib/rules/token/table-pipe-style.d.ts.map +1 -0
  389. package/lib/rules/token/table-pipe-style.js.map +1 -0
  390. package/lib/rules/token/ul-indent.d.ts.map +1 -0
  391. package/lib/rules/token/ul-indent.js.map +1 -0
  392. package/lib/rules/token/ul-style.d.ts.map +1 -0
  393. package/lib/rules/token/ul-style.js +70 -0
  394. package/lib/rules/token/ul-style.js.map +1 -0
  395. package/lib/rules/types.d.ts +62 -0
  396. package/lib/rules/types.d.ts.map +1 -0
  397. package/lib/rules/utils.d.ts +18 -0
  398. package/lib/rules/utils.d.ts.map +1 -0
  399. package/lib/rules/utils.js +75 -0
  400. package/lib/rules/utils.js.map +1 -0
  401. package/lib/scopes/extractor.d.ts +6 -0
  402. package/lib/scopes/extractor.d.ts.map +1 -0
  403. package/lib/scopes/extractor.js +398 -0
  404. package/lib/scopes/extractor.js.map +1 -0
  405. package/lib/scopes/selector.d.ts +26 -0
  406. package/lib/scopes/selector.d.ts.map +1 -0
  407. package/lib/scopes/selector.js +78 -0
  408. package/lib/scopes/selector.js.map +1 -0
  409. package/lib/scopes/sentences.d.ts +13 -0
  410. package/lib/scopes/sentences.d.ts.map +1 -0
  411. package/lib/scopes/sentences.js +237 -0
  412. package/lib/scopes/sentences.js.map +1 -0
  413. package/lib/scopes/types.d.ts +35 -0
  414. package/lib/scopes/types.d.ts.map +1 -0
  415. package/lib/scopes/vocabulary.d.ts +12 -0
  416. package/lib/scopes/vocabulary.d.ts.map +1 -0
  417. package/lib/scopes/vocabulary.js +56 -0
  418. package/lib/scopes/vocabulary.js.map +1 -0
  419. package/lib/types/assertions.d.ts +97 -0
  420. package/lib/types/assertions.d.ts.map +1 -0
  421. package/lib/types/reporting.d.ts +23 -0
  422. package/lib/types/reporting.d.ts.map +1 -0
  423. package/lib/types/rules.d.ts +53 -0
  424. package/lib/types/rules.d.ts.map +1 -0
  425. package/lib/types/validation.d.ts +5 -0
  426. package/lib/types/validation.d.ts.map +1 -0
  427. package/lib/utils/is-plain-object.d.ts +2 -0
  428. package/lib/utils/is-plain-object.d.ts.map +1 -0
  429. package/lib/utils/is-plain-object.js +5 -0
  430. package/lib/utils/is-plain-object.js.map +1 -0
  431. package/package.json +58 -61
  432. package/presets/google/PROVENANCE.md +217 -221
  433. package/presets/inclusive-language/PROVENANCE.md +20 -19
  434. package/presets/microsoft/PROVENANCE.md +242 -243
  435. package/presets/plain-language/PROVENANCE.md +66 -60
  436. package/presets/technical-english/PROVENANCE.md +5 -5
  437. package/skills/recheck-config/SKILL.md +70 -20
  438. package/skills/recheck-lint/SKILL.md +37 -13
  439. package/dist/cli.d.ts +0 -3
  440. package/dist/cli.d.ts.map +0 -1
  441. package/dist/cli.js +0 -228
  442. package/dist/cli.js.map +0 -1
  443. package/dist/commands/baseline.d.ts +0 -10
  444. package/dist/commands/baseline.d.ts.map +0 -1
  445. package/dist/commands/baseline.js +0 -59
  446. package/dist/commands/baseline.js.map +0 -1
  447. package/dist/commands/markdoc-schema.d.ts +0 -17
  448. package/dist/commands/markdoc-schema.d.ts.map +0 -1
  449. package/dist/commands/markdoc-schema.js +0 -127
  450. package/dist/commands/markdoc-schema.js.map +0 -1
  451. package/dist/commands/readability.d.ts +0 -10
  452. package/dist/commands/readability.d.ts.map +0 -1
  453. package/dist/commands/readability.js +0 -90
  454. package/dist/commands/readability.js.map +0 -1
  455. package/dist/commands/run.d.ts +0 -21
  456. package/dist/commands/run.d.ts.map +0 -1
  457. package/dist/commands/run.js +0 -236
  458. package/dist/commands/run.js.map +0 -1
  459. package/dist/commands/validate.d.ts +0 -8
  460. package/dist/commands/validate.d.ts.map +0 -1
  461. package/dist/commands/validate.js +0 -43
  462. package/dist/commands/validate.js.map +0 -1
  463. package/dist/config/load.d.ts +0 -33
  464. package/dist/config/load.d.ts.map +0 -1
  465. package/dist/config/load.js +0 -98
  466. package/dist/config/load.js.map +0 -1
  467. package/dist/config/presets/api-descriptions.d.ts +0 -8
  468. package/dist/config/presets/api-descriptions.d.ts.map +0 -1
  469. package/dist/config/presets/api-descriptions.js +0 -24
  470. package/dist/config/presets/api-descriptions.js.map +0 -1
  471. package/dist/config/presets/google.d.ts +0 -3
  472. package/dist/config/presets/google.d.ts.map +0 -1
  473. package/dist/config/presets/google.js +0 -1671
  474. package/dist/config/presets/google.js.map +0 -1
  475. package/dist/config/presets/inclusive-language.d.ts +0 -3
  476. package/dist/config/presets/inclusive-language.d.ts.map +0 -1
  477. package/dist/config/presets/inclusive-language.js +0 -321
  478. package/dist/config/presets/inclusive-language.js.map +0 -1
  479. package/dist/config/presets/index.d.ts +0 -67
  480. package/dist/config/presets/index.d.ts.map +0 -1
  481. package/dist/config/presets/index.js +0 -141
  482. package/dist/config/presets/index.js.map +0 -1
  483. package/dist/config/presets/markdoc.d.ts +0 -21
  484. package/dist/config/presets/markdoc.d.ts.map +0 -1
  485. package/dist/config/presets/markdoc.js +0 -101
  486. package/dist/config/presets/markdoc.js.map +0 -1
  487. package/dist/config/presets/markdown-relaxed.d.ts +0 -3
  488. package/dist/config/presets/markdown-relaxed.d.ts.map +0 -1
  489. package/dist/config/presets/markdown-relaxed.js +0 -98
  490. package/dist/config/presets/markdown-relaxed.js.map +0 -1
  491. package/dist/config/presets/markdown.d.ts +0 -43
  492. package/dist/config/presets/markdown.d.ts.map +0 -1
  493. package/dist/config/presets/markdown.js +0 -132
  494. package/dist/config/presets/markdown.js.map +0 -1
  495. package/dist/config/presets/microsoft.d.ts +0 -3
  496. package/dist/config/presets/microsoft.d.ts.map +0 -1
  497. package/dist/config/presets/microsoft.js +0 -2268
  498. package/dist/config/presets/microsoft.js.map +0 -1
  499. package/dist/config/presets/minimal.d.ts +0 -3
  500. package/dist/config/presets/minimal.d.ts.map +0 -1
  501. package/dist/config/presets/minimal.js +0 -21
  502. package/dist/config/presets/minimal.js.map +0 -1
  503. package/dist/config/presets/plain-language.d.ts +0 -3
  504. package/dist/config/presets/plain-language.d.ts.map +0 -1
  505. package/dist/config/presets/plain-language.js +0 -351
  506. package/dist/config/presets/plain-language.js.map +0 -1
  507. package/dist/config/presets/prose.d.ts +0 -52
  508. package/dist/config/presets/prose.d.ts.map +0 -1
  509. package/dist/config/presets/prose.js +0 -138
  510. package/dist/config/presets/prose.js.map +0 -1
  511. package/dist/config/presets/technical-english.d.ts +0 -34
  512. package/dist/config/presets/technical-english.d.ts.map +0 -1
  513. package/dist/config/presets/technical-english.js +0 -79
  514. package/dist/config/presets/technical-english.js.map +0 -1
  515. package/dist/config/schema.d.ts +0 -226
  516. package/dist/config/schema.d.ts.map +0 -1
  517. package/dist/config/schema.js +0 -196
  518. package/dist/config/schema.js.map +0 -1
  519. package/dist/config/validate.d.ts +0 -22
  520. package/dist/config/validate.d.ts.map +0 -1
  521. package/dist/config/validate.js +0 -1315
  522. package/dist/config/validate.js.map +0 -1
  523. package/dist/core/auto-fix.d.ts.map +0 -1
  524. package/dist/core/auto-fix.js +0 -101
  525. package/dist/core/auto-fix.js.map +0 -1
  526. package/dist/core/baseline.d.ts +0 -49
  527. package/dist/core/baseline.d.ts.map +0 -1
  528. package/dist/core/baseline.js +0 -0
  529. package/dist/core/baseline.js.map +0 -1
  530. package/dist/core/case-preserve.d.ts +0 -46
  531. package/dist/core/case-preserve.d.ts.map +0 -1
  532. package/dist/core/case-preserve.js +0 -57
  533. package/dist/core/case-preserve.js.map +0 -1
  534. package/dist/core/directives.d.ts.map +0 -1
  535. package/dist/core/directives.js +0 -73
  536. package/dist/core/directives.js.map +0 -1
  537. package/dist/core/files.d.ts +0 -69
  538. package/dist/core/files.d.ts.map +0 -1
  539. package/dist/core/files.js +0 -260
  540. package/dist/core/files.js.map +0 -1
  541. package/dist/core/inline-code.d.ts +0 -87
  542. package/dist/core/inline-code.d.ts.map +0 -1
  543. package/dist/core/inline-code.js +0 -104
  544. package/dist/core/inline-code.js.map +0 -1
  545. package/dist/core/line-endings.d.ts +0 -32
  546. package/dist/core/line-endings.d.ts.map +0 -1
  547. package/dist/core/line-endings.js +0 -65
  548. package/dist/core/line-endings.js.map +0 -1
  549. package/dist/core/markdoc-tags.d.ts +0 -79
  550. package/dist/core/markdoc-tags.d.ts.map +0 -1
  551. package/dist/core/markdoc-tags.js +0 -131
  552. package/dist/core/markdoc-tags.js.map +0 -1
  553. package/dist/core/prose-extract.d.ts +0 -6
  554. package/dist/core/prose-extract.d.ts.map +0 -1
  555. package/dist/core/prose-extract.js +0 -79
  556. package/dist/core/prose-extract.js.map +0 -1
  557. package/dist/core/readability.d.ts +0 -17
  558. package/dist/core/readability.d.ts.map +0 -1
  559. package/dist/core/readability.js +0 -37
  560. package/dist/core/readability.js.map +0 -1
  561. package/dist/core/rule-filters.d.ts +0 -45
  562. package/dist/core/rule-filters.d.ts.map +0 -1
  563. package/dist/core/rule-filters.js +0 -118
  564. package/dist/core/rule-filters.js.map +0 -1
  565. package/dist/core/runner.d.ts +0 -98
  566. package/dist/core/runner.d.ts.map +0 -1
  567. package/dist/core/runner.js +0 -401
  568. package/dist/core/runner.js.map +0 -1
  569. package/dist/core/timing.d.ts +0 -24
  570. package/dist/core/timing.d.ts.map +0 -1
  571. package/dist/core/timing.js +0 -39
  572. package/dist/core/timing.js.map +0 -1
  573. package/dist/data/markdoc-realm-schema.d.ts.map +0 -1
  574. package/dist/data/markdoc-realm-schema.js +0 -798
  575. package/dist/data/markdoc-realm-schema.js.map +0 -1
  576. package/dist/data/proper-nouns.d.ts.map +0 -1
  577. package/dist/data/proper-nouns.js +0 -47
  578. package/dist/data/proper-nouns.js.map +0 -1
  579. package/dist/data/realm-front-matter-schema.d.ts +0 -28
  580. package/dist/data/realm-front-matter-schema.d.ts.map +0 -1
  581. package/dist/data/realm-front-matter-schema.js +0 -73
  582. package/dist/data/realm-front-matter-schema.js.map +0 -1
  583. package/dist/index.d.ts +0 -90
  584. package/dist/index.d.ts.map +0 -1
  585. package/dist/index.js +0 -152
  586. package/dist/index.js.map +0 -1
  587. package/dist/metrics/formulas.d.ts +0 -17
  588. package/dist/metrics/formulas.d.ts.map +0 -1
  589. package/dist/metrics/formulas.js +0 -70
  590. package/dist/metrics/formulas.js.map +0 -1
  591. package/dist/metrics/statistics.d.ts +0 -27
  592. package/dist/metrics/statistics.d.ts.map +0 -1
  593. package/dist/metrics/statistics.js +0 -56
  594. package/dist/metrics/statistics.js.map +0 -1
  595. package/dist/parser/index.d.ts +0 -19
  596. package/dist/parser/index.d.ts.map +0 -1
  597. package/dist/parser/index.js +0 -174
  598. package/dist/parser/index.js.map +0 -1
  599. package/dist/parser/markdoc/extract-statics.d.ts +0 -45
  600. package/dist/parser/markdoc/extract-statics.d.ts.map +0 -1
  601. package/dist/parser/markdoc/extract-statics.js +0 -139
  602. package/dist/parser/markdoc/extract-statics.js.map +0 -1
  603. package/dist/parser/markdoc/pairing.d.ts +0 -63
  604. package/dist/parser/markdoc/pairing.d.ts.map +0 -1
  605. package/dist/parser/markdoc/pairing.js +0 -94
  606. package/dist/parser/markdoc/pairing.js.map +0 -1
  607. package/dist/parser/markdoc/schema.d.ts +0 -85
  608. package/dist/parser/markdoc/schema.d.ts.map +0 -1
  609. package/dist/parser/markdoc/schema.js +0 -86
  610. package/dist/parser/markdoc/schema.js.map +0 -1
  611. package/dist/parser/markdoc/span.d.ts +0 -64
  612. package/dist/parser/markdoc/span.d.ts.map +0 -1
  613. package/dist/parser/markdoc/span.js +0 -729
  614. package/dist/parser/markdoc/span.js.map +0 -1
  615. package/dist/parser/markdoc/structure.d.ts +0 -28
  616. package/dist/parser/markdoc/structure.d.ts.map +0 -1
  617. package/dist/parser/markdoc/structure.js +0 -153
  618. package/dist/parser/markdoc/structure.js.map +0 -1
  619. package/dist/parser/markdoc/syntax.d.ts +0 -44
  620. package/dist/parser/markdoc/syntax.d.ts.map +0 -1
  621. package/dist/parser/markdoc/syntax.js +0 -317
  622. package/dist/parser/markdoc/syntax.js.map +0 -1
  623. package/dist/parser/types.d.ts.map +0 -1
  624. package/dist/reporter/fixes.d.ts +0 -3
  625. package/dist/reporter/fixes.d.ts.map +0 -1
  626. package/dist/reporter/fixes.js +0 -41
  627. package/dist/reporter/fixes.js.map +0 -1
  628. package/dist/reporter/formats/github-actions.d.ts +0 -6
  629. package/dist/reporter/formats/github-actions.d.ts.map +0 -1
  630. package/dist/reporter/formats/github-actions.js +0 -20
  631. package/dist/reporter/formats/github-actions.js.map +0 -1
  632. package/dist/reporter/formats/json.d.ts +0 -10
  633. package/dist/reporter/formats/json.d.ts.map +0 -1
  634. package/dist/reporter/formats/json.js +0 -25
  635. package/dist/reporter/formats/json.js.map +0 -1
  636. package/dist/reporter/formats/sarif.d.ts +0 -10
  637. package/dist/reporter/formats/sarif.d.ts.map +0 -1
  638. package/dist/reporter/formats/sarif.js +0 -66
  639. package/dist/reporter/formats/sarif.js.map +0 -1
  640. package/dist/reporter/formats/table.d.ts +0 -6
  641. package/dist/reporter/formats/table.d.ts.map +0 -1
  642. package/dist/reporter/formats/table.js +0 -43
  643. package/dist/reporter/formats/table.js.map +0 -1
  644. package/dist/reporter/index.d.ts +0 -10
  645. package/dist/reporter/index.d.ts.map +0 -1
  646. package/dist/reporter/index.js +0 -42
  647. package/dist/reporter/index.js.map +0 -1
  648. package/dist/reporter/prioritize-problems.d.ts +0 -7
  649. package/dist/reporter/prioritize-problems.d.ts.map +0 -1
  650. package/dist/reporter/prioritize-problems.js +0 -29
  651. package/dist/reporter/prioritize-problems.js.map +0 -1
  652. package/dist/reporter/statistics.d.ts +0 -10
  653. package/dist/reporter/statistics.d.ts.map +0 -1
  654. package/dist/reporter/statistics.js +0 -52
  655. package/dist/reporter/statistics.js.map +0 -1
  656. package/dist/reporter/summary.d.ts +0 -10
  657. package/dist/reporter/summary.d.ts.map +0 -1
  658. package/dist/reporter/summary.js +0 -49
  659. package/dist/reporter/summary.js.map +0 -1
  660. package/dist/rules/registry.d.ts +0 -15
  661. package/dist/rules/registry.d.ts.map +0 -1
  662. package/dist/rules/registry.js +0 -81
  663. package/dist/rules/registry.js.map +0 -1
  664. package/dist/rules/scope/capitalization.d.ts.map +0 -1
  665. package/dist/rules/scope/capitalization.js +0 -164
  666. package/dist/rules/scope/capitalization.js.map +0 -1
  667. package/dist/rules/scope/conditional.d.ts.map +0 -1
  668. package/dist/rules/scope/conditional.js +0 -112
  669. package/dist/rules/scope/conditional.js.map +0 -1
  670. package/dist/rules/scope/consistency.d.ts.map +0 -1
  671. package/dist/rules/scope/consistency.js +0 -179
  672. package/dist/rules/scope/consistency.js.map +0 -1
  673. package/dist/rules/scope/length.d.ts.map +0 -1
  674. package/dist/rules/scope/length.js +0 -73
  675. package/dist/rules/scope/length.js.map +0 -1
  676. package/dist/rules/scope/max-image-size.d.ts.map +0 -1
  677. package/dist/rules/scope/max-image-size.js +0 -65
  678. package/dist/rules/scope/max-image-size.js.map +0 -1
  679. package/dist/rules/scope/metric.d.ts +0 -5
  680. package/dist/rules/scope/metric.d.ts.map +0 -1
  681. package/dist/rules/scope/metric.js +0 -74
  682. package/dist/rules/scope/metric.js.map +0 -1
  683. package/dist/rules/scope/occurrence.d.ts.map +0 -1
  684. package/dist/rules/scope/occurrence.js +0 -48
  685. package/dist/rules/scope/occurrence.js.map +0 -1
  686. package/dist/rules/scope/pattern.d.ts.map +0 -1
  687. package/dist/rules/scope/pattern.js +0 -75
  688. package/dist/rules/scope/pattern.js.map +0 -1
  689. package/dist/rules/scope/repetition.d.ts.map +0 -1
  690. package/dist/rules/scope/repetition.js +0 -139
  691. package/dist/rules/scope/repetition.js.map +0 -1
  692. package/dist/rules/scope/semantic-line-breaks.d.ts.map +0 -1
  693. package/dist/rules/scope/semantic-line-breaks.js +0 -236
  694. package/dist/rules/scope/semantic-line-breaks.js.map +0 -1
  695. package/dist/rules/scope/spelling.d.ts +0 -27
  696. package/dist/rules/scope/spelling.d.ts.map +0 -1
  697. package/dist/rules/scope/spelling.js +0 -227
  698. package/dist/rules/scope/spelling.js.map +0 -1
  699. package/dist/rules/scope/swap.d.ts.map +0 -1
  700. package/dist/rules/scope/swap.js +0 -149
  701. package/dist/rules/scope/swap.js.map +0 -1
  702. package/dist/rules/scope/title-case.d.ts +0 -46
  703. package/dist/rules/scope/title-case.d.ts.map +0 -1
  704. package/dist/rules/scope/title-case.js +0 -301
  705. package/dist/rules/scope/title-case.js.map +0 -1
  706. package/dist/rules/token/blanks-around-fences.d.ts.map +0 -1
  707. package/dist/rules/token/blanks-around-fences.js.map +0 -1
  708. package/dist/rules/token/blanks-around-headings.d.ts.map +0 -1
  709. package/dist/rules/token/blanks-around-headings.js +0 -108
  710. package/dist/rules/token/blanks-around-headings.js.map +0 -1
  711. package/dist/rules/token/blanks-around-lists.d.ts.map +0 -1
  712. package/dist/rules/token/blanks-around-lists.js +0 -56
  713. package/dist/rules/token/blanks-around-lists.js.map +0 -1
  714. package/dist/rules/token/blanks-around-tables.d.ts.map +0 -1
  715. package/dist/rules/token/blanks-around-tables.js +0 -42
  716. package/dist/rules/token/blanks-around-tables.js.map +0 -1
  717. package/dist/rules/token/code-block-style.d.ts.map +0 -1
  718. package/dist/rules/token/code-block-style.js.map +0 -1
  719. package/dist/rules/token/code-fence-style.d.ts.map +0 -1
  720. package/dist/rules/token/code-fence-style.js.map +0 -1
  721. package/dist/rules/token/commands-show-output.d.ts.map +0 -1
  722. package/dist/rules/token/commands-show-output.js.map +0 -1
  723. package/dist/rules/token/descriptive-link-text.d.ts.map +0 -1
  724. package/dist/rules/token/descriptive-link-text.js +0 -53
  725. package/dist/rules/token/descriptive-link-text.js.map +0 -1
  726. package/dist/rules/token/fenced-code-language.d.ts.map +0 -1
  727. package/dist/rules/token/fenced-code-language.js.map +0 -1
  728. package/dist/rules/token/first-line-h1.d.ts.map +0 -1
  729. package/dist/rules/token/first-line-h1.js +0 -107
  730. package/dist/rules/token/first-line-h1.js.map +0 -1
  731. package/dist/rules/token/front-matter.d.ts.map +0 -1
  732. package/dist/rules/token/front-matter.js +0 -177
  733. package/dist/rules/token/front-matter.js.map +0 -1
  734. package/dist/rules/token/heading-increment.d.ts.map +0 -1
  735. package/dist/rules/token/heading-increment.js +0 -27
  736. package/dist/rules/token/heading-increment.js.map +0 -1
  737. package/dist/rules/token/heading-start-left.d.ts.map +0 -1
  738. package/dist/rules/token/heading-start-left.js.map +0 -1
  739. package/dist/rules/token/heading-style.d.ts.map +0 -1
  740. package/dist/rules/token/heading-style.js.map +0 -1
  741. package/dist/rules/token/helpers.d.ts +0 -313
  742. package/dist/rules/token/helpers.d.ts.map +0 -1
  743. package/dist/rules/token/helpers.js +0 -746
  744. package/dist/rules/token/helpers.js.map +0 -1
  745. package/dist/rules/token/hr-style.d.ts.map +0 -1
  746. package/dist/rules/token/hr-style.js.map +0 -1
  747. package/dist/rules/token/index.d.ts +0 -76
  748. package/dist/rules/token/index.d.ts.map +0 -1
  749. package/dist/rules/token/index.js +0 -229
  750. package/dist/rules/token/index.js.map +0 -1
  751. package/dist/rules/token/line-length.d.ts.map +0 -1
  752. package/dist/rules/token/line-length.js +0 -120
  753. package/dist/rules/token/line-length.js.map +0 -1
  754. package/dist/rules/token/link-fragments.d.ts.map +0 -1
  755. package/dist/rules/token/link-fragments.js +0 -393
  756. package/dist/rules/token/link-fragments.js.map +0 -1
  757. package/dist/rules/token/link-image-reference-definitions.d.ts.map +0 -1
  758. package/dist/rules/token/link-image-reference-definitions.js.map +0 -1
  759. package/dist/rules/token/link-image-style.d.ts.map +0 -1
  760. package/dist/rules/token/link-image-style.js +0 -131
  761. package/dist/rules/token/link-image-style.js.map +0 -1
  762. package/dist/rules/token/list-indent.d.ts.map +0 -1
  763. package/dist/rules/token/list-indent.js.map +0 -1
  764. package/dist/rules/token/list-length.d.ts.map +0 -1
  765. package/dist/rules/token/list-length.js +0 -55
  766. package/dist/rules/token/list-length.js.map +0 -1
  767. package/dist/rules/token/list-marker-space.d.ts.map +0 -1
  768. package/dist/rules/token/list-marker-space.js.map +0 -1
  769. package/dist/rules/token/markdoc-attributes.d.ts.map +0 -1
  770. package/dist/rules/token/markdoc-attributes.js +0 -269
  771. package/dist/rules/token/markdoc-attributes.js.map +0 -1
  772. package/dist/rules/token/markdoc-pairing.d.ts.map +0 -1
  773. package/dist/rules/token/markdoc-pairing.js +0 -73
  774. package/dist/rules/token/markdoc-pairing.js.map +0 -1
  775. package/dist/rules/token/markdoc-syntax.d.ts.map +0 -1
  776. package/dist/rules/token/markdoc-syntax.js +0 -119
  777. package/dist/rules/token/markdoc-syntax.js.map +0 -1
  778. package/dist/rules/token/markdoc-unknown-tag.d.ts.map +0 -1
  779. package/dist/rules/token/markdoc-unknown-tag.js +0 -64
  780. package/dist/rules/token/markdoc-unknown-tag.js.map +0 -1
  781. package/dist/rules/token/no-alt-text.d.ts.map +0 -1
  782. package/dist/rules/token/no-alt-text.js +0 -47
  783. package/dist/rules/token/no-alt-text.js.map +0 -1
  784. package/dist/rules/token/no-bare-urls.d.ts.map +0 -1
  785. package/dist/rules/token/no-bare-urls.js +0 -88
  786. package/dist/rules/token/no-bare-urls.js.map +0 -1
  787. package/dist/rules/token/no-blanks-blockquote.d.ts.map +0 -1
  788. package/dist/rules/token/no-blanks-blockquote.js.map +0 -1
  789. package/dist/rules/token/no-duplicate-heading.d.ts.map +0 -1
  790. package/dist/rules/token/no-duplicate-heading.js +0 -101
  791. package/dist/rules/token/no-duplicate-heading.js.map +0 -1
  792. package/dist/rules/token/no-duplicate-link-destinations.d.ts.map +0 -1
  793. package/dist/rules/token/no-duplicate-link-destinations.js +0 -65
  794. package/dist/rules/token/no-duplicate-link-destinations.js.map +0 -1
  795. package/dist/rules/token/no-emphasis-as-heading.d.ts.map +0 -1
  796. package/dist/rules/token/no-emphasis-as-heading.js +0 -44
  797. package/dist/rules/token/no-emphasis-as-heading.js.map +0 -1
  798. package/dist/rules/token/no-empty-headings.d.ts.map +0 -1
  799. package/dist/rules/token/no-empty-headings.js +0 -28
  800. package/dist/rules/token/no-empty-headings.js.map +0 -1
  801. package/dist/rules/token/no-empty-links.d.ts.map +0 -1
  802. package/dist/rules/token/no-empty-links.js.map +0 -1
  803. package/dist/rules/token/no-hard-tabs.d.ts.map +0 -1
  804. package/dist/rules/token/no-hard-tabs.js.map +0 -1
  805. package/dist/rules/token/no-inline-html.d.ts.map +0 -1
  806. package/dist/rules/token/no-inline-html.js +0 -45
  807. package/dist/rules/token/no-inline-html.js.map +0 -1
  808. package/dist/rules/token/no-multiple-blanks.d.ts.map +0 -1
  809. package/dist/rules/token/no-multiple-blanks.js.map +0 -1
  810. package/dist/rules/token/no-multiple-space-atx.d.ts +0 -13
  811. package/dist/rules/token/no-multiple-space-atx.d.ts.map +0 -1
  812. package/dist/rules/token/no-multiple-space-atx.js +0 -50
  813. package/dist/rules/token/no-multiple-space-atx.js.map +0 -1
  814. package/dist/rules/token/no-multiple-space-closed-atx.d.ts.map +0 -1
  815. package/dist/rules/token/no-multiple-space-closed-atx.js.map +0 -1
  816. package/dist/rules/token/no-reversed-links.d.ts.map +0 -1
  817. package/dist/rules/token/no-reversed-links.js.map +0 -1
  818. package/dist/rules/token/no-space-in-code.d.ts.map +0 -1
  819. package/dist/rules/token/no-space-in-code.js.map +0 -1
  820. package/dist/rules/token/no-space-in-emphasis.d.ts.map +0 -1
  821. package/dist/rules/token/no-space-in-emphasis.js +0 -79
  822. package/dist/rules/token/no-space-in-emphasis.js.map +0 -1
  823. package/dist/rules/token/no-space-in-links.d.ts.map +0 -1
  824. package/dist/rules/token/no-space-in-links.js.map +0 -1
  825. package/dist/rules/token/no-trailing-punctuation.d.ts.map +0 -1
  826. package/dist/rules/token/no-trailing-punctuation.js.map +0 -1
  827. package/dist/rules/token/no-trailing-spaces.d.ts.map +0 -1
  828. package/dist/rules/token/no-trailing-spaces.js.map +0 -1
  829. package/dist/rules/token/ol-prefix.d.ts.map +0 -1
  830. package/dist/rules/token/ol-prefix.js.map +0 -1
  831. package/dist/rules/token/proper-names.d.ts.map +0 -1
  832. package/dist/rules/token/proper-names.js +0 -93
  833. package/dist/rules/token/proper-names.js.map +0 -1
  834. package/dist/rules/token/reference-links-images.d.ts.map +0 -1
  835. package/dist/rules/token/reference-links-images.js.map +0 -1
  836. package/dist/rules/token/required-headings.d.ts.map +0 -1
  837. package/dist/rules/token/required-headings.js +0 -83
  838. package/dist/rules/token/required-headings.js.map +0 -1
  839. package/dist/rules/token/single-h1.d.ts.map +0 -1
  840. package/dist/rules/token/single-h1.js +0 -56
  841. package/dist/rules/token/single-h1.js.map +0 -1
  842. package/dist/rules/token/table-column-count.d.ts.map +0 -1
  843. package/dist/rules/token/table-column-count.js.map +0 -1
  844. package/dist/rules/token/table-column-style.d.ts.map +0 -1
  845. package/dist/rules/token/table-column-style.js +0 -179
  846. package/dist/rules/token/table-column-style.js.map +0 -1
  847. package/dist/rules/token/table-pipe-style.d.ts.map +0 -1
  848. package/dist/rules/token/table-pipe-style.js.map +0 -1
  849. package/dist/rules/token/ul-indent.d.ts.map +0 -1
  850. package/dist/rules/token/ul-indent.js.map +0 -1
  851. package/dist/rules/token/ul-style.d.ts.map +0 -1
  852. package/dist/rules/token/ul-style.js +0 -80
  853. package/dist/rules/token/ul-style.js.map +0 -1
  854. package/dist/rules/types.d.ts +0 -83
  855. package/dist/rules/types.d.ts.map +0 -1
  856. package/dist/rules/utils.d.ts +0 -31
  857. package/dist/rules/utils.d.ts.map +0 -1
  858. package/dist/rules/utils.js +0 -90
  859. package/dist/rules/utils.js.map +0 -1
  860. package/dist/scopes/extractor.d.ts +0 -7
  861. package/dist/scopes/extractor.d.ts.map +0 -1
  862. package/dist/scopes/extractor.js +0 -536
  863. package/dist/scopes/extractor.js.map +0 -1
  864. package/dist/scopes/selector.d.ts +0 -51
  865. package/dist/scopes/selector.d.ts.map +0 -1
  866. package/dist/scopes/selector.js +0 -121
  867. package/dist/scopes/selector.js.map +0 -1
  868. package/dist/scopes/sentences.d.ts +0 -15
  869. package/dist/scopes/sentences.d.ts.map +0 -1
  870. package/dist/scopes/sentences.js +0 -202
  871. package/dist/scopes/sentences.js.map +0 -1
  872. package/dist/scopes/types.d.ts +0 -46
  873. package/dist/scopes/types.d.ts.map +0 -1
  874. package/dist/scopes/vocabulary.d.ts +0 -16
  875. package/dist/scopes/vocabulary.d.ts.map +0 -1
  876. package/dist/scopes/vocabulary.js +0 -70
  877. package/dist/scopes/vocabulary.js.map +0 -1
  878. package/dist/types/assertions.d.ts +0 -121
  879. package/dist/types/assertions.d.ts.map +0 -1
  880. package/dist/types/reporting.d.ts +0 -32
  881. package/dist/types/reporting.d.ts.map +0 -1
  882. package/dist/types/rules.d.ts +0 -41
  883. package/dist/types/rules.d.ts.map +0 -1
  884. package/dist/types/validation.d.ts +0 -6
  885. package/dist/types/validation.d.ts.map +0 -1
  886. /package/{dist → lib}/core/auto-fix.d.ts +0 -0
  887. /package/{dist → lib}/core/directives.d.ts +0 -0
  888. /package/{dist → lib}/data/markdoc-realm-schema.d.ts +0 -0
  889. /package/{dist → lib}/data/proper-nouns.d.ts +0 -0
  890. /package/{dist → lib}/metrics/index.d.ts +0 -0
  891. /package/{dist → lib}/metrics/index.d.ts.map +0 -0
  892. /package/{dist → lib}/metrics/index.js +0 -0
  893. /package/{dist → lib}/metrics/index.js.map +0 -0
  894. /package/{dist → lib}/parser/types.d.ts +0 -0
  895. /package/{dist → lib}/parser/types.js +0 -0
  896. /package/{dist → lib}/parser/types.js.map +0 -0
  897. /package/{dist → lib}/rules/scope/capitalization.d.ts +0 -0
  898. /package/{dist → lib}/rules/scope/conditional.d.ts +0 -0
  899. /package/{dist → lib}/rules/scope/consistency.d.ts +0 -0
  900. /package/{dist → lib}/rules/scope/length.d.ts +0 -0
  901. /package/{dist → lib}/rules/scope/max-image-size.d.ts +0 -0
  902. /package/{dist → lib}/rules/scope/occurrence.d.ts +0 -0
  903. /package/{dist → lib}/rules/scope/pattern.d.ts +0 -0
  904. /package/{dist → lib}/rules/scope/repetition.d.ts +0 -0
  905. /package/{dist → lib}/rules/scope/semantic-line-breaks.d.ts +0 -0
  906. /package/{dist → lib}/rules/scope/swap.d.ts +0 -0
  907. /package/{dist → lib}/rules/token/blanks-around-fences.d.ts +0 -0
  908. /package/{dist → lib}/rules/token/blanks-around-fences.js +0 -0
  909. /package/{dist → lib}/rules/token/blanks-around-headings.d.ts +0 -0
  910. /package/{dist → lib}/rules/token/blanks-around-lists.d.ts +0 -0
  911. /package/{dist → lib}/rules/token/blanks-around-tables.d.ts +0 -0
  912. /package/{dist → lib}/rules/token/code-block-style.d.ts +0 -0
  913. /package/{dist → lib}/rules/token/code-block-style.js +0 -0
  914. /package/{dist → lib}/rules/token/code-fence-style.d.ts +0 -0
  915. /package/{dist → lib}/rules/token/code-fence-style.js +0 -0
  916. /package/{dist → lib}/rules/token/commands-show-output.d.ts +0 -0
  917. /package/{dist → lib}/rules/token/commands-show-output.js +0 -0
  918. /package/{dist → lib}/rules/token/descriptive-link-text.d.ts +0 -0
  919. /package/{dist → lib}/rules/token/emphasis-style.d.ts +0 -0
  920. /package/{dist → lib}/rules/token/emphasis-style.d.ts.map +0 -0
  921. /package/{dist → lib}/rules/token/emphasis-style.js +0 -0
  922. /package/{dist → lib}/rules/token/emphasis-style.js.map +0 -0
  923. /package/{dist → lib}/rules/token/fenced-code-language.d.ts +0 -0
  924. /package/{dist → lib}/rules/token/fenced-code-language.js +0 -0
  925. /package/{dist → lib}/rules/token/first-line-h1.d.ts +0 -0
  926. /package/{dist → lib}/rules/token/front-matter.d.ts +0 -0
  927. /package/{dist → lib}/rules/token/heading-increment.d.ts +0 -0
  928. /package/{dist → lib}/rules/token/heading-start-left.d.ts +0 -0
  929. /package/{dist → lib}/rules/token/heading-start-left.js +0 -0
  930. /package/{dist → lib}/rules/token/heading-style.d.ts +0 -0
  931. /package/{dist → lib}/rules/token/heading-style.js +0 -0
  932. /package/{dist → lib}/rules/token/hr-style.d.ts +0 -0
  933. /package/{dist → lib}/rules/token/hr-style.js +0 -0
  934. /package/{dist → lib}/rules/token/line-length.d.ts +0 -0
  935. /package/{dist → lib}/rules/token/link-fragments.d.ts +0 -0
  936. /package/{dist → lib}/rules/token/link-image-reference-definitions.d.ts +0 -0
  937. /package/{dist → lib}/rules/token/link-image-reference-definitions.js +0 -0
  938. /package/{dist → lib}/rules/token/link-image-style.d.ts +0 -0
  939. /package/{dist → lib}/rules/token/list-indent.d.ts +0 -0
  940. /package/{dist → lib}/rules/token/list-indent.js +0 -0
  941. /package/{dist → lib}/rules/token/list-length.d.ts +0 -0
  942. /package/{dist → lib}/rules/token/list-marker-space.d.ts +0 -0
  943. /package/{dist → lib}/rules/token/list-marker-space.js +0 -0
  944. /package/{dist → lib}/rules/token/markdoc-attributes.d.ts +0 -0
  945. /package/{dist → lib}/rules/token/markdoc-pairing.d.ts +0 -0
  946. /package/{dist → lib}/rules/token/markdoc-syntax.d.ts +0 -0
  947. /package/{dist → lib}/rules/token/markdoc-unknown-tag.d.ts +0 -0
  948. /package/{dist → lib}/rules/token/messages.d.ts +0 -0
  949. /package/{dist → lib}/rules/token/messages.d.ts.map +0 -0
  950. /package/{dist → lib}/rules/token/messages.js +0 -0
  951. /package/{dist → lib}/rules/token/messages.js.map +0 -0
  952. /package/{dist → lib}/rules/token/no-alt-text.d.ts +0 -0
  953. /package/{dist → lib}/rules/token/no-bare-urls.d.ts +0 -0
  954. /package/{dist → lib}/rules/token/no-blanks-blockquote.d.ts +0 -0
  955. /package/{dist → lib}/rules/token/no-blanks-blockquote.js +0 -0
  956. /package/{dist → lib}/rules/token/no-duplicate-heading.d.ts +0 -0
  957. /package/{dist → lib}/rules/token/no-duplicate-link-destinations.d.ts +0 -0
  958. /package/{dist → lib}/rules/token/no-emphasis-as-heading.d.ts +0 -0
  959. /package/{dist → lib}/rules/token/no-empty-headings.d.ts +0 -0
  960. /package/{dist → lib}/rules/token/no-empty-links.d.ts +0 -0
  961. /package/{dist → lib}/rules/token/no-empty-links.js +0 -0
  962. /package/{dist → lib}/rules/token/no-hard-tabs.d.ts +0 -0
  963. /package/{dist → lib}/rules/token/no-hard-tabs.js +0 -0
  964. /package/{dist → lib}/rules/token/no-inline-html.d.ts +0 -0
  965. /package/{dist → lib}/rules/token/no-missing-space-atx.d.ts +0 -0
  966. /package/{dist → lib}/rules/token/no-missing-space-atx.d.ts.map +0 -0
  967. /package/{dist → lib}/rules/token/no-missing-space-atx.js +0 -0
  968. /package/{dist → lib}/rules/token/no-missing-space-atx.js.map +0 -0
  969. /package/{dist → lib}/rules/token/no-missing-space-closed-atx.d.ts +0 -0
  970. /package/{dist → lib}/rules/token/no-missing-space-closed-atx.d.ts.map +0 -0
  971. /package/{dist → lib}/rules/token/no-missing-space-closed-atx.js +0 -0
  972. /package/{dist → lib}/rules/token/no-missing-space-closed-atx.js.map +0 -0
  973. /package/{dist → lib}/rules/token/no-multiple-blanks.d.ts +0 -0
  974. /package/{dist → lib}/rules/token/no-multiple-blanks.js +0 -0
  975. /package/{dist → lib}/rules/token/no-multiple-space-blockquote.d.ts +0 -0
  976. /package/{dist → lib}/rules/token/no-multiple-space-blockquote.d.ts.map +0 -0
  977. /package/{dist → lib}/rules/token/no-multiple-space-blockquote.js +0 -0
  978. /package/{dist → lib}/rules/token/no-multiple-space-blockquote.js.map +0 -0
  979. /package/{dist → lib}/rules/token/no-multiple-space-closed-atx.d.ts +0 -0
  980. /package/{dist → lib}/rules/token/no-multiple-space-closed-atx.js +0 -0
  981. /package/{dist → lib}/rules/token/no-reversed-links.d.ts +0 -0
  982. /package/{dist → lib}/rules/token/no-reversed-links.js +0 -0
  983. /package/{dist → lib}/rules/token/no-space-in-code.d.ts +0 -0
  984. /package/{dist → lib}/rules/token/no-space-in-code.js +0 -0
  985. /package/{dist → lib}/rules/token/no-space-in-emphasis.d.ts +0 -0
  986. /package/{dist → lib}/rules/token/no-space-in-links.d.ts +0 -0
  987. /package/{dist → lib}/rules/token/no-space-in-links.js +0 -0
  988. /package/{dist → lib}/rules/token/no-trailing-punctuation.d.ts +0 -0
  989. /package/{dist → lib}/rules/token/no-trailing-punctuation.js +0 -0
  990. /package/{dist → lib}/rules/token/no-trailing-spaces.d.ts +0 -0
  991. /package/{dist → lib}/rules/token/no-trailing-spaces.js +0 -0
  992. /package/{dist → lib}/rules/token/ol-prefix.d.ts +0 -0
  993. /package/{dist → lib}/rules/token/ol-prefix.js +0 -0
  994. /package/{dist → lib}/rules/token/proper-names.d.ts +0 -0
  995. /package/{dist → lib}/rules/token/reference-links-images.d.ts +0 -0
  996. /package/{dist → lib}/rules/token/reference-links-images.js +0 -0
  997. /package/{dist → lib}/rules/token/required-headings.d.ts +0 -0
  998. /package/{dist → lib}/rules/token/single-h1.d.ts +0 -0
  999. /package/{dist → lib}/rules/token/single-trailing-newline.d.ts +0 -0
  1000. /package/{dist → lib}/rules/token/single-trailing-newline.d.ts.map +0 -0
  1001. /package/{dist → lib}/rules/token/single-trailing-newline.js +0 -0
  1002. /package/{dist → lib}/rules/token/single-trailing-newline.js.map +0 -0
  1003. /package/{dist → lib}/rules/token/strong-style.d.ts +0 -0
  1004. /package/{dist → lib}/rules/token/strong-style.d.ts.map +0 -0
  1005. /package/{dist → lib}/rules/token/strong-style.js +0 -0
  1006. /package/{dist → lib}/rules/token/strong-style.js.map +0 -0
  1007. /package/{dist → lib}/rules/token/table-column-count.d.ts +0 -0
  1008. /package/{dist → lib}/rules/token/table-column-count.js +0 -0
  1009. /package/{dist → lib}/rules/token/table-column-style.d.ts +0 -0
  1010. /package/{dist → lib}/rules/token/table-pipe-style.d.ts +0 -0
  1011. /package/{dist → lib}/rules/token/table-pipe-style.js +0 -0
  1012. /package/{dist → lib}/rules/token/ul-indent.d.ts +0 -0
  1013. /package/{dist → lib}/rules/token/ul-indent.js +0 -0
  1014. /package/{dist → lib}/rules/token/ul-style.d.ts +0 -0
  1015. /package/{dist → lib}/rules/types.js +0 -0
  1016. /package/{dist → lib}/rules/types.js.map +0 -0
  1017. /package/{dist → lib}/scopes/types.js +0 -0
  1018. /package/{dist → lib}/scopes/types.js.map +0 -0
  1019. /package/{dist → lib}/types/assertions.js +0 -0
  1020. /package/{dist → lib}/types/assertions.js.map +0 -0
  1021. /package/{dist → lib}/types/index.d.ts +0 -0
  1022. /package/{dist → lib}/types/index.d.ts.map +0 -0
  1023. /package/{dist → lib}/types/index.js +0 -0
  1024. /package/{dist → lib}/types/index.js.map +0 -0
  1025. /package/{dist → lib}/types/problems.d.ts +0 -0
  1026. /package/{dist → lib}/types/problems.d.ts.map +0 -0
  1027. /package/{dist → lib}/types/problems.js +0 -0
  1028. /package/{dist → lib}/types/problems.js.map +0 -0
  1029. /package/{dist → lib}/types/reporting.js +0 -0
  1030. /package/{dist → lib}/types/reporting.js.map +0 -0
  1031. /package/{dist → lib}/types/rules.js +0 -0
  1032. /package/{dist → lib}/types/rules.js.map +0 -0
  1033. /package/{dist → lib}/types/validation.js +0 -0
  1034. /package/{dist → lib}/types/validation.js.map +0 -0
package/README.md CHANGED
@@ -1,1908 +1,93 @@
1
- # Recheck
1
+ # @redocly/recheck
2
2
 
3
- Recheck combines a **markdown linter** (structure/format — full markdownlint rule parity: 53 built-in rules with auto-fix) and a **prose linter** (style/voice — Vale-style scopes like `sentence`/`paragraph`/`heading` with `swap`/`pattern`/`repetition`/`consistency`/`capitalization` rules) in **one tool with one simple YAML config** — replacing a markdownlint + Vale combo with one line:
3
+ The Markdown and prose linting engine behind `redocly recheck`.
4
4
 
5
- ```yaml
6
- extends: [recheck/markdown, recheck/prose]
7
- ```
8
-
9
- Recheck is also built to be **embedded by other tools**: it exposes a library-first API (`parseMarkdown`, `extractScopes`, `lintContent`, `lintFiles`, `runRules` — see [Library API](#library-api)) so tools like Redocly CLI's `lint` command can add markdown and prose linting too, including linting markdown strings embedded inside API descriptions.
10
-
11
- ## Features
12
-
13
- ✅ **Modern Scope-Based Architecture**
14
- - File-first processing: each file is parsed once into a [micromark](https://github.com/micromark/micromark) AST, then segmented into scopes
15
- - Full scope vocabulary: `all`, `raw`, `summary` (alias: `default`), `sentence`, `paragraph`, `heading` (+ `heading.h1`-`h6`), `code`, `list-item`, `blockquote`, `table.header`, `table.cell`, `markdoc.tag`, `frontmatter`, `html`, `comment`, `alt`, `link`
16
- - Selector syntax for precise targeting: `~` negates a term, `&` joins terms into a conjunction — e.g. `scope: ['~blockquote & ~heading']`
17
- - Vale-compatible scope notation (e.g., `heading.h1`, `heading.h2`)
18
- - Efficient rule indexing for fast processing at scale
19
-
20
- ✅ **Flexible Configuration Format**
21
- - Modern `assertions`-based rule definitions
22
- - `severity` levels: `off`, `info`, `warn`, `error`
23
- - Array-based scope targeting for precise control
24
- - Comprehensive JSON Schema validation
25
-
26
- ✅ **Production-Ready Engine**
27
- - High-performance JavaScript engine optimized for large repositories
28
- - Successfully processes 300+ files with 1,000+ issues efficiently
29
- - Built-in rules for common content quality checks
30
- - Safe auto-fix capabilities for appropriate rules
31
-
32
- ✅ **Developer-Friendly CLI**
33
- - Table, JSON, SARIF, and GitHub Actions output formats for CI/CD integration
34
- - Universal output-path option for file export
35
- - Inline PR annotations with GitHub Actions format
36
- - Severity filtering and detailed statistics
37
- - Auto-fix with granular control
38
- - Comprehensive error reporting
39
-
40
- ## Installation
41
-
42
- ```bash
43
- # Navigate to the recheck package
44
- cd packages/recheck
45
-
46
- # Install dependencies
47
- pnpm install
48
-
49
- # Build the project
50
- pnpm build
51
- ```
52
-
53
- **Contributing to Recheck itself** (build-cache problems, `pnpm parity`'s required `--corpus` flag, the `generate-examples.mjs`/`oxfmt` coupling) is covered in [CONTRIBUTING.md](CONTRIBUTING.md), not here — this README is for people adopting Recheck as a linter.
54
-
55
- ## Usage
56
-
57
- ### Validate Configuration
58
-
59
- ```bash
60
- # Validate with explicit config file
61
- node dist/cli.js --validate-config --config recheck.example.yaml
62
-
63
- # Auto-discover config file in current directory
64
- node dist/cli.js --validate-config
65
- ```
66
-
67
- ### Run content linting
68
-
69
- ```bash
70
- # Run on current directory
71
- node dist/cli.js . --config recheck.example.yaml
72
-
73
- # Run on specific file
74
- node dist/cli.js README.md --config recheck.example.yaml
75
-
76
- # Filter by severity (only show errors)
77
- node dist/cli.js . --severity error
78
-
79
- # Show all enabled rules (info and above)
80
- node dist/cli.js . --severity info
81
-
82
- # Work one rule at a time. This helps you clear a large list of findings.
83
- # Give the name that the report shows, or the full config key. Use the flag
84
- # more than one time for more than one rule. A rule from a namespace other
85
- # than `recheck/` keeps that namespace: use `google/passive-voice`.
86
- node dist/cli.js . --rule semantic-line-breaks
87
- node dist/cli.js . -r us-spelling -r recheck/oxford-comma
88
-
89
- # ...and its inverse, to silence a rule you have already triaged
90
- node dist/cli.js . --exclude-rule semantic-line-breaks
91
-
92
- # Use --rule with --fix to clear one rule's findings across all documents
93
- node dist/cli.js . --rule semantic-line-breaks --fix
94
-
95
- # A name that matches no rule in your config is an error, not an empty run:
96
- # a misspelled filter that reported "no issues" would look the same as a
97
- # clean document set. The error message lists the rules your config loaded.
98
-
99
- # Output formats (table is default)
100
- node dist/cli.js . --output table # Human-readable table (default)
101
- node dist/cli.js . --output json # Structured JSON for CI
102
- node dist/cli.js . --output sarif # SARIF format for security tools
103
- node dist/cli.js . --output github-actions # GitHub Actions annotations (inline PR comments)
104
-
105
- # Show detailed statistics
106
- node dist/cli.js . --stats
107
-
108
- # Auto-fix safe issues (35 fixable rules total: swap + semantic-line-breaks
109
- # natively, plus 33 of the 53 markdownlint-parity rules — see the rule table
110
- # under "Markdownlint parity" below for the full per-rule breakdown)
111
- node dist/cli.js . --fix
112
-
113
- # Combine auto-fix with statistics
114
- node dist/cli.js . --fix --stats
115
-
116
- # Limit annotations for CI (applies to file output, default: 20)
117
- node dist/cli.js . --annotations-limit 50
118
-
119
- # Output to file (works with all formats)
120
- node dist/cli.js . --output json --output-path report.json
121
- node dist/cli.js . --output sarif --output-path recheck.sarif
122
- node dist/cli.js . --output json --output-path limited.json --annotations-limit 50
123
-
124
- # Emit run summary to a file (json or text)
125
- node dist/cli.js . --summary json --summary-path recheck-summary.json
126
-
127
- # Scan only changed files (via file list or stdin)
128
- # From file:
129
- node dist/cli.js . --changed-only --changed-list changed.txt
130
- # Or with stdin:
131
- git diff --name-only origin/main... | node dist/cli.js . --changed-only
132
- ```
133
-
134
- ## Library API
135
-
136
- The CLI is a thin wrapper around a public library API, published from `packages/recheck`'s `dist/index.js`.
137
- This is the intended integration point for embedding Recheck in another tool (a build step, an editor extension, or another CLI like Redocly CLI's `lint`) rather than shelling out:
138
-
139
- ```ts
140
- import { lintContent, lintFiles } from '@redocly/recheck';
141
-
142
- // A config is a flat map of `recheck/<rule>` -> rule definition — the same
143
- // shape a YAML config file resolves to once `extends` presets are expanded.
144
- // Load from YAML (via `loadConfig`, which resolves `extends` for you) or
145
- // build one programmatically, as here:
146
- const config = {
147
- 'recheck/no-hard-tabs': {
148
- severity: 'error' as const,
149
- message: 'Hard tabs',
150
- assertions: { 'no-hard-tabs': {} },
151
- },
152
- };
153
-
154
- // Lint an in-memory string — no file I/O. Useful for linting markdown that
155
- // isn't on disk, e.g. a `description` field pulled out of an OpenAPI document.
156
- const problems = await lintContent('# Title\n\nSome *text*.\n', config);
157
-
158
- // Lint files from disk, optionally writing auto-fixes back:
159
- const { problems: fileProblems, fixedFiles } = await lintFiles(['README.md'], config, {
160
- fix: true,
161
- });
162
- ```
163
-
164
- Key exports:
165
-
166
- - **`parseMarkdown(content, options?)`** — parses a markdown string into a micromark-based token tree, once.
167
- Every other API in this list builds on this tree rather than re-parsing.
168
- `options.markdoc` is a boolean here: `true` also tokenizes `{% ... %}` Markdoc tag spans into `markdocTag` tokens, and `false` or omitted gives you the same tree as passing no options at all.
169
- The [object form](#markdoc-aware-linting-markdoc-true) (`{ schema, extend }`) is a config-file concept only — it resolves down to this boolean before any file is parsed, and `ParseOptions.markdoc` does not accept it.
170
- - **`extractScopes(tree, content)`** — segments a parsed token tree into Vale-style scopes (`sentence`, `paragraph`, `heading`, `list-item`, `blockquote`, `table.cell`, etc.) for prose/style rules to run against.
171
- - **`lintContent(content, config, opts?)`** — lints a single in-memory markdown string against a config; no disk access.
172
- Rules that need on-disk facts (e.g. `max-image-size`) require `opts.metadata` to be supplied by the caller.
173
- - **`lintFiles(paths, config, opts?)`** — lints markdown files from disk; pass `{ fix: true }` to also write auto-fixes back, looping lint → fix → re-lint until the file converges.
174
- Files that can't be read are skipped (with a console warning) and reported in the returned `skippedFiles` (`{ path, reason }[]`), so callers can detect incomplete coverage programmatically.
175
- `opts.root` sets the lint root that image-metadata loading is confined to (default `process.cwd()`) — image refs resolving outside it are treated as missing without touching the disk.
176
- `opts.maxProblems` caps the total problems collected: once a file's lint pushes the run to the cap, later files aren't linted at all and the returned `truncated` flag is set.
177
- - **`runRules(files, rules)`** — the lower-level engine entry point for callers that already have a `NormalizedRule[]` (e.g. from `loadConfig`) and want to run against an explicit in-memory file list, bypassing `lintFiles`'s own config loading/validation.
178
- Under `{ fix: true }` its `RunResult` separates the fixes that genuinely landed (`fixes`) from proposals dropped by overlap resolution (`skippedFixes`).
179
- - **`applyFixesToContent(content, fixes)`** — applies `Fix` edits to a string, preserving the file's own line endings (CRLF files stay CRLF).
180
- Returns `{ content, applied, skipped }`: every input fix is classified as genuinely applied or skipped (overlapping edits, out-of-range lines), so callers can report what actually changed rather than every proposal.
181
- - **`computeTextStatistics(prose)`** — computes word/sentence/syllable/character/complex-word counts for a plain prose string (not markdown — extract prose from a scope first).
182
- Sentence counting reuses `splitSentences` internally, so it agrees with the rest of the engine on sentence boundaries.
183
- Tokenization is ASCII-only by design (accented or non-Latin letters don't count as word characters), so readability scores are meaningful for English prose.
184
- - **`computeReadability(formula, stats)`** — scores a `TextStatistics` object with one of six standard readability formulas: `flesch-reading-ease`, `flesch-kincaid-grade`, `gunning-fog`, `smog`, `coleman-liau`, `automated-readability`.
185
- Returns `0` (rather than `NaN`/`Infinity`) when `stats.words` or `stats.sentences` is `0`.
186
- - **`TECHNICAL_PROPER_NOUNS`** — the [built-in technical proper-noun vocabulary](#built-in-technical-proper-noun-vocabulary) `capitalization`/`spelling` consume by default; re-exported so you can read it or build your own tooling around the same list.
187
-
188
- This is exactly the surface a host tool needs to add both markdown-structure linting and prose/style linting to content it already has in memory — for example, linting the markdown inside an OpenAPI `description` field without writing it to a temp file first.
189
-
190
- ## Configuration Format
191
-
192
- Configuration uses a modern `assertions`-based format with `severity` levels and flexible scope targeting.
193
-
194
- A top-level `excludes` applies to every rule, so a path you never lint is stated once rather than repeated on each rule.
195
- It is merged ahead of a rule's own `excludes`, which still apply:
196
-
197
- ```yaml
198
- excludes:
199
- - "**/_partials/**"
200
- - "CHANGELOG.md"
201
- ```
202
-
203
- ```yaml
204
- recheck/us-spelling:
205
- scope: all # default
206
- severity: error
207
- message: 'Use the US spelling "%s" instead of British "%s".'
208
- link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-us-spelling-avoid-non-english-words
209
- appliesTo:
210
- - "docs/**" # Only apply to documentation
211
- assertions:
212
- swap:
213
- ignoreCase: true
214
- wordBoundary: true
215
- pairs:
216
- color: colour
217
- behavior: behaviour
218
- organize: organise
219
- exceptions:
220
- files: [docs/style-guide.md]
221
- lines:
222
- - "British spellings such as 'color'"
223
-
224
- recheck/no-gerund-headings:
225
- severity: error
226
- scope:
227
- - heading.h1
228
- - heading.h2
229
- - heading.h3
230
- message: 'Do not start headings with a gerund.'
231
- excludes:
232
- - "**/drafts/**" # Exclude draft documents
233
- assertions:
234
- pattern:
235
- ignoreCase: true
236
- tokens:
237
- - '^\\w*ing.*'
238
-
239
- recheck/config-line-length:
240
- severity: error
241
- message: 'Config docs: keep lines under %s characters.'
242
- appliesTo:
243
- - "docs/config/**" # Only apply to config documentation
244
- assertions:
245
- line-length:
246
- lineLength: 100
247
- codeBlocks: false
248
-
249
- recheck/ul-style-dash:
250
- severity: error
251
- message: "Use '-' for unordered list bullets."
252
- excludes:
253
- - "**/examples/**" # Allow mixed styles in examples
254
- assertions:
255
- ul-style:
256
- style: dash
257
- ```
258
-
259
- ## Baseline
260
-
261
- A baseline lets a team adopt recheck on a large document set with no cleanup project first: record the findings that exist today, then fail only on new ones.
262
-
263
- ```bash
264
- recheck --generate-baseline # writes recheck-baseline.yaml next to your config
265
- ```
266
-
267
- Activate it with one config line:
268
-
269
- ```yaml
270
- baseline: ./recheck-baseline.yaml
271
- ```
272
-
273
- The file stores one count per file per rule, errors only, sorted for stable diffs:
274
-
275
- ```yaml
276
- version: 1
277
- files:
278
- docs/index.md:
279
- recheck/semantic-line-breaks: 3
280
- ```
281
-
282
- With the baseline active, `recheck`:
283
-
284
- - **suppresses** findings whose (file, rule) count matches the baseline, and reports how many matched;
285
- - **fails** when a count rises — the group's findings are printed with `(baseline 3, found 5)` context;
286
- - **fails** when a count falls, because the baseline is stale — the message says to run `recheck --generate-baseline` and commit the result.
287
- Counts only step down, so the file equals reality at every green commit.
288
-
289
- Warnings are never baselined; they do not affect exit codes.
290
- Partial runs (`--rule`, `--changed-only`, a narrower path) compare only the files they scanned and the rules they ran, so they never false-alarm about what they did not see.
291
- Line numbers are deliberately not stored: counts survive unrelated edits, and a baseline diff in review reads as "this PR pays down 4 findings."
292
- A renamed file is a new path with no budget, so its pre-existing findings report as new until you regenerate — the baseline diff then shows the counts moving from the old path to the new one.
293
-
294
- ## Readability
295
-
296
- `recheck --readability` reports scores per file: Flesch reading ease, Flesch-Kincaid grade, Automated Readability Index (ARI), words, and sentences, plus medians.
297
- ARI is a grade level computed from exact character counts, with no syllable heuristic, which makes it steadier on technical vocabulary.
298
- It is score-shaped, not rule-shaped: it never gates and always exits 0 when it ran.
299
- To gate on a bound, use the `metric` assertion — both read the same prose and the same formulas, so they can never disagree.
300
-
301
- ```bash
302
- recheck docs --readability
303
- recheck docs --readability --output json
304
- recheck docs --readability --changed-only < changed.txt # score only listed files
305
- ```
306
-
307
- The score reads flowing prose the way standard readability tools do: headings, code, and Markdoc tags are excluded, and every block ends a sentence.
308
- A file with no prose reports `—` (null in JSON) rather than a fake zero.
309
- In CI, run it twice — once on the PR head and once on the merge-base worktree — and join on file to show each changed page's score change.
310
-
311
- ## Agent skills
312
-
313
- Agents write a growing share of markdown, and a skill makes each one a recheck user with no person in the loop.
314
- Two skills ship in the npm package under `skills/`:
315
-
316
- - **`recheck-lint`** — run recheck on touched markdown before committing or outputting it, fix errors, and never suppress findings to pass.
317
- - **`recheck-config`** — write and tune `recheck.yaml`: measure the corpus, set severities from counts, prefer fixes over exceptions, and adopt a baseline for large corpora.
318
-
319
- To use them with Claude Code, copy them into your project:
320
-
321
- ```bash
322
- cp -r node_modules/@redocly/recheck/skills/recheck-lint .claude/skills/
323
- cp -r node_modules/@redocly/recheck/skills/recheck-config .claude/skills/
324
- ```
325
-
326
- Each skill is one `SKILL.md` with a trigger description and instructions, so other agent runtimes can adapt them with a rename.
327
-
328
- ## Exceptions
329
-
330
- Rules can be configured with exceptions to skip specific files or lines:
331
-
332
- ### File Exceptions
333
-
334
- Skip entire files using glob patterns or exact matches:
335
-
336
- ```yaml
337
- recheck/us-spelling:
338
- # ... other config
339
- exceptions:
340
- files:
341
- - "docs/style-guide.md" # Exact filename
342
- - "docs/api/*.md" # Glob pattern
343
- - "**/CHANGELOG.md" # Recursive glob
344
- ```
345
-
346
- **File matching supports:**
347
- - **Basename matching**: `style-guide.md` matches any file with that name
348
- - **Relative path matching**: `docs/style-guide.md` matches the specific path
349
- - **Glob patterns**: `docs/*.md` matches all markdown files in docs directory
350
-
351
- ### Line Exceptions
352
-
353
- Skip specific lines using fragment matching:
354
-
355
- ```yaml
356
- recheck/no-trailing-spaces:
357
- # ... other config
358
- exceptions:
359
- lines:
360
- - "British spellings such as" # Fragment match
361
- - "Code example:" # Beginning of line
362
- - "// ignore-lint" # Comment-based exception
363
- ```
364
-
365
- **Line matching behavior:**
366
- - **Fragment matching**: If the line contains the exception text anywhere, it's skipped
367
- - **Case-sensitive**: `"Code Example"` does not match `"code example"`
368
- - **Multiple patterns**: Any matching pattern will skip the line
369
-
370
- ### Exception Examples
371
-
372
- ```yaml
373
- # Skip documentation style guides for spelling rules
374
- recheck/us-spelling:
375
- exceptions:
376
- files: ["docs/style-guide.md", "**/*style*"]
377
- lines: ["British spellings such as 'colour'"]
378
-
379
- # Skip auto-generated files and code blocks
380
- recheck/no-trailing-spaces:
381
- exceptions:
382
- files: ["**/generated/**", "CHANGELOG.md"]
383
- lines: ["```", "Code example:", "// formatter-ignore"]
384
- ```
385
-
386
- ## Inline Directives
387
-
388
- Beyond config-level `exceptions`, individual Markdown files can silence rules
389
- inline with HTML comments — the same mechanism ESLint/Vale users expect.
390
- A
391
- directive names rules by their **short name** (`oxford-comma`) or **full
392
- name** (`recheck/oxford-comma`) — both work.
393
- A directive is inert inside a
394
- fenced code block (it has to be real, parsed HTML, not just matching text).
395
-
396
- ```markdown
397
- <!-- recheck-disable -->
398
- Everything below this point is unchecked, for every rule.
399
- <!-- recheck-enable -->
400
- Checking resumes here.
401
-
402
- <!-- recheck-disable oxford-comma us-spelling -->
403
- Only these two rules are off from here on.
404
- <!-- recheck-enable oxford-comma -->
405
- us-spelling is still off; oxford-comma is back on.
406
-
407
- <!-- recheck-disable-next-line oxford-comma -->
408
- This one line is exempt from oxford-comma; the rest of the file isn't.
409
-
410
- <!-- recheck-disable-file -->
411
- Nothing in this file is linted at all, no matter where this comment sits.
412
- ```
413
-
414
- The five forms:
415
-
416
- | Directive | Effect |
417
- | --- | --- |
418
- | `<!-- recheck-disable -->` | Disables **all** rules from this line to the end of the file, or until a matching `recheck-enable`. |
419
- | `<!-- recheck-disable rule… -->` | Disables only the **listed** rules from this line on (same end conditions). |
420
- | `<!-- recheck-enable -->` | Re-enables all rules (or, with rule names, only the listed ones) from this line on. |
421
- | `<!-- recheck-disable-next-line -->` | Disables all rules (or, with rule names, only the listed ones) for exactly the next line. |
422
- | `<!-- recheck-disable-file -->` | Disables every rule for the whole file, regardless of where the comment appears. |
423
-
424
- Rule naming: list one or more rules space-separated, by short name
425
- (`oxford-comma`) or full name (`recheck/oxford-comma`) — both work on every
426
- form that accepts names; omitting names targets every rule.
427
- Naming a rule
428
- that isn't configured produces a warning (`recheck-directive`, severity
429
- `warn`) pointing at the directive's line — useful for catching a typo in
430
- the disabled rule name — but disables nothing.
431
-
432
- ## Rule Types and Assertions
433
-
434
- ### Assertion Types
435
-
436
- Rules are defined using `assertions` that specify their behavior:
437
-
438
- #### Swap Assertions (`swap`)
439
- Text replacement with configurable options.
440
- **Fixable**: each match is replaced with its pair's value, with the matched text's own casing applied to the replacement --
441
- an all-lowercase match inserts the replacement as configured, a Capitalized match capitalizes just the replacement's first word,
442
- and an ALL-CAPS match (2+ letters) uppercases the whole replacement;
443
- any other (mixed-case) casing is left as configured, since it carries no reliable intent to infer.
444
- This matters most with `ignoreCase: true`: without it, a sentence-initial `'Behaviour'` would be fixed to literal `'behavior'`, silently lowercasing the start of the sentence -- with it, it fixes to `'Behavior'`.
445
- (With `keysAreRegex: true`, casing is inferred from the MATCHED text, not the regex key, so this applies uniformly to regex keys too.)
446
- When two pairs' matches overlap in the source (a compound key together with the shorter keys it contains), the longest match wins and is reported and fixed as one span.
447
-
448
- ```yaml
449
- assertions:
450
- swap:
451
- ignoreCase: true
452
- wordBoundary: true
453
- pairs:
454
- color: colour
455
- behavior: behaviour
456
- ```
457
-
458
- | Option | Type | Required | Description |
459
- | --- | --- | --- | --- |
460
- | `pairs` | `object` | Yes | Find → replace entries: each key is searched for in the segment's content and reported/fixed with its value. Keys must be non-empty strings; values must be strings. |
461
- | `ignoreCase` | `boolean` | No | Matches keys case-insensitively. Default `false`. |
462
- | `wordBoundary` | `boolean` | No | Wraps each key in `\b...\b` so only whole words match. Default `false`. |
463
- | `keysAreRegex` | `boolean` | No | Keys are literal text by default; set `true` to treat each key as a regex (for example, `favou?rite` matches both spellings). An invalid regex key is ignored and matches nothing; the rule's other pairs still apply. Default `false`. |
464
- | `includeCode` | `boolean` | No | Matches inside inline code spans (`` `like this` ``) are skipped by default, so a pair like `master: primary` doesn't fire inside `` `git checkout master` ``. Set `true` to scan inline code too. Default `false`. |
465
-
466
- A missing or empty `pairs`, an empty-string key, a non-string replacement value, or an unknown option key under `swap` is a validation error.
467
-
468
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the replacement, 2nd = the matched text** — with the pair `utilize: use`, the message `'Use "%s" instead of "%s".'` renders as `'Use "use" instead of "utilize".'`
469
-
470
- #### Pattern Assertions (`pattern`)
471
- Regex-based pattern matching:
472
- ```yaml
473
- assertions:
474
- pattern:
475
- ignoreCase: true
476
- tokens:
477
- - '^\\w*ing.*'
478
- ```
479
-
480
- | Option | Type | Required | Description |
481
- | --- | --- | --- | --- |
482
- | `tokens` | `string[]` | Yes | Regex patterns matched against each segment's content. An invalid regex is caught and silently produces zero problems rather than crashing the run. |
483
- | `ignoreCase` | `boolean` | No | Matches every token case-insensitively. Default `false`. |
484
- | `includeCode` | `boolean` | No | Matches inside inline code spans (`` `like this` ``) are skipped by default, so a token like `master` doesn't fire inside `` `git checkout master` ``. Set `true` to scan inline code too. Default `false`. |
485
-
486
- #### Occurrence Assertions (`occurrence`)
487
- Vale-parity `occurrence` check: counts regex matches within each scoped segment and flags the segment when the count falls outside `[min, max]`.
488
- `min: 1` with no `max` acts as an existence check — it flags a segment where the pattern is missing entirely.
489
-
490
- ```yaml
491
- assertions:
492
- occurrence:
493
- pattern: '[.!?]'
494
- max: 3
495
- ```
496
-
497
- | Option | Type | Required | Description |
498
- | --- | --- | --- | --- |
499
- | `pattern` | `string` | Yes | Regex matched against each segment's content (whole segment, not per-line). |
500
- | `min` | `number` | At least one of `min`/`max` | Minimum allowed match count; fewer matches is a violation. `min: 1` with no `max` reads as "the pattern must be present". |
501
- | `max` | `number` | At least one of `min`/`max` | Maximum allowed match count; more matches is a violation. |
502
- | `ignoreCase` | `boolean` | No | Matches `pattern` case-insensitively. Default `false`. |
503
-
504
- Omitting both `min` and `max` is a validation error — an occurrence assertion with no bound can never report anything.
505
- An unknown option key under `occurrence` is likewise a validation error.
506
-
507
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the actual match count, 2nd = the bound that was violated** (`min` or `max`, whichever applied), e.g. `'Too many sentences (%s found, max %s).'` → `'Too many sentences (4 found, max 3).'`.
508
- Not fixable (detection-only): a count-based violation has no single match position to anchor an edit to.
509
-
510
- #### Repetition Assertions (`repetition`)
511
- Vale-parity `repetition` check: flags an adjacent repeated word — two tokens matching `pattern`, separated only by whitespace (which may include a single hard-wrap newline), so `'the theory'` is not flagged (different words) but `'the the'` and a hard-wrapped `'the\nthe rest'` are.
512
- **Fixable**: collapses the pair back to one occurrence, keeping the FIRST token's casing/text (so `'The the'` fixes to `'The'`, not `'the'`).
513
-
514
- ```yaml
515
- assertions:
516
- repetition:
517
- ignoreCase: true # default
518
- ```
519
-
520
- | Option | Type | Required | Description |
521
- | --- | --- | --- | --- |
522
- | `pattern` | `string` | No | Regex used to tokenize each segment's content. Default `\w+`. |
523
- | `ignoreCase` | `boolean` | No | Compares adjacent tokens case-insensitively. Default **`true`** — unlike every other assertion's case-sensitive default, since `'The the'` is the overwhelmingly common typo this check exists to catch. Set `false` to require an exact-case repeat. |
524
-
525
- An unknown option key under `repetition` is a validation error, as is a non-string `pattern` or a non-boolean `ignoreCase`; both options are optional, so an empty `repetition: {}` is valid.
526
-
527
- The rule's `message` gets one positional `%s` substitution: the repeated word itself, e.g. `'Repeated word "%s".'` → `'Repeated word "the".'`.
528
- Fix idempotency holds under repeated `--fix` passes: `'the the the'` converges to `'the'`.
529
-
530
- #### Consistency Assertions (`consistency`)
531
- Vale-parity `consistency` check: each `either` entry declares one alternative group — the key and the value are the two variants (both matched as literals with word boundaries, like `swap` keys).
532
- Whichever variant appears **first in the file (by source order)** wins file-wide; every later occurrence of the other variant is flagged.
533
- **Fixable**: each later occurrence is replaced with the winning variant **literally as written in `either`** — unlike `swap`, the losing match's own casing is not preserved here (with `ignoreCase: true`, a later `'Behaviour'` in a `behavior`-first document fixes to `'behavior'`).
534
-
535
- ```yaml
536
- assertions:
537
- consistency:
538
- either:
539
- behavior: behaviour
540
- color: colour
541
- ```
542
-
543
- | Option | Type | Required | Description |
544
- | --- | --- | --- | --- |
545
- | `either` | `object` | Yes | Map of variant pairs; key and value are the two alternatives of one group. Each pair gets its own independent first-seen winner. Must be non-empty. |
546
- | `ignoreCase` | `boolean` | No | Matches variants case-insensitively (so `'Behaviour'` counts as an occurrence of `behaviour`). Default `false`. |
547
-
548
- Omitting `either`, leaving it empty, or giving it non-string or empty-string keys or values is a validation error — a consistency assertion with no variant pairs can never report anything, and an empty-string key would otherwise reach the scan loop as a zero-width regex that never terminates.
549
- An unknown option key under `consistency` is likewise a validation error.
550
-
551
- Matches from overlapping scopes (e.g. `scope: [paragraph, sentence]`, where every sentence segment sits inside its paragraph segment) are deduplicated by source position before the winner is decided, so each occurrence is counted — and fixed — exactly once.
552
-
553
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the offending (later) match, 2nd = the first-seen winner**, e.g. `'Inconsistent spelling: "%s" conflicts with first-seen "%s".'` → `'Inconsistent spelling: "behaviour" conflicts with first-seen "behavior".'`.
554
-
555
- #### Conditional Assertions (`conditional`)
556
- Vale-parity `conditional` check: if `first` (a regex pattern) matches anywhere within the rule's scoped segments, `second` (a regex pattern) must exist **somewhere in the whole file** — checked against the full raw file content, not just the rule's own scope, so a `second` match sitting inside a code block still satisfies a rule scoped to `paragraph`.
557
- When `second` is absent file-wide, every `first` match becomes its own problem, at its exact source position.
558
- **Detection-only** (not fixable) — there is no single well-defined edit that would "introduce" `second`.
559
-
560
- ```yaml
561
- assertions:
562
- conditional:
563
- first: '\bTODO\b'
564
- second: '\bDONE\b'
565
- ```
566
-
567
- | Option | Type | Required | Description |
568
- | --- | --- | --- | --- |
569
- | `first` | `string` | Yes | Regex; if it matches anywhere in the rule's scoped segments, `second` is required. Non-empty. |
570
- | `second` | `string` | Yes | Regex; must match somewhere in the whole file content once `first` has matched. Non-empty. |
571
- | `ignoreCase` | `boolean` | No | Matches both `first` and `second` case-insensitively. Default `false`. |
572
-
573
- Unlike `swap`/`consistency`'s escaped-literal variants, `first` and `second` are raw user regex patterns (like `pattern`'s `tokens`).
574
- Missing, empty, or non-string `first`/`second` is a validation error, as is an unknown option key or a non-boolean `ignoreCase` — but `first`/`second` are **not** validated as compilable regexes at config-load time; an invalid regex in either one silently produces zero problems at runtime instead (same convention as `pattern`).
575
-
576
- Matches from overlapping scopes (e.g. `scope: [paragraph, sentence]`) are deduplicated by source position, so each occurrence of `first` is reported exactly once.
577
-
578
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the offending `first` match, 2nd = the `second` pattern that was never introduced**, e.g. `'"%s" appears but "%s" was never introduced.'` → `'"TODO" appears but "DONE" was never introduced.'`.
579
-
580
- #### Capitalization Assertions (`capitalization`)
581
- Vale-parity `capitalization` check: flags (and — for four of its `match` values — fixes) a scoped segment whose text doesn't already match the required casing.
582
- `match` is one of `$title`, `$sentence`, `$lower`, `$upper`, or else a **custom regex** the whole segment text must satisfy.
583
-
584
- ```yaml
585
- assertions:
586
- capitalization:
587
- match: $title
588
- style: chicago # optional, default 'ap' — only affects $title
589
- exceptions: [GitHub, iPhone]
590
- ```
591
-
592
- | Option | Type | Required | Description |
593
- | --- | --- | --- | --- |
594
- | `match` | `string` | Yes | `$title`, `$sentence`, `$lower`, `$upper`, or a regex the whole segment text must satisfy. Non-empty. |
595
- | `style` | `'ap' \| 'chicago'` | No | Stopword list `$title` uses (see below). Default `'ap'`. Accepted alongside any `match`, but only has an effect on `$title` — a documented no-op elsewhere, not a validation error. |
596
- | `exceptions` | `string[]` | No | Words (or phrases — see below) kept in their EXACT as-written casing from this list, everywhere they appear — including the first/last word — overriding every other rule. Unioned with the [built-in technical proper-noun vocabulary](#built-in-technical-proper-noun-vocabulary) unless `builtinVocabulary: false`. |
597
- | `builtinVocabulary` | `boolean` | No | Default `true`. Whether [`TECHNICAL_PROPER_NOUNS`](#built-in-technical-proper-noun-vocabulary) is unioned into `exceptions`. Set `false` for a closed vocabulary of only this rule's own `exceptions`. |
598
-
599
- An `exceptions` entry containing whitespace or a dot (e.g. `Node.js`, `VS Code`) is matched as a whole PHRASE against the segment text — case-insensitively but otherwise literally, longest-match-first when phrases overlap — and preserved verbatim, instead of being looked up per word.
600
-
601
- Unknown option keys, a missing/empty `match`, an invalid `style`, a non-string-array `exceptions`, or a non-boolean `builtinVocabulary` are all validation errors.
602
-
603
- **`$title`** — AP or Chicago title case, implemented in `rules/scope/title-case.ts`'s `apTitleCase`/`chicagoTitleCase`:
604
- - The first and last word are **always** capitalized, regardless of any stopword list.
605
- - A hyphenated compound (e.g. `well-known`) runs **each hyphen part** through the same stopword test a standalone word gets for the active style — `well-known` → `Well-Known`, but `editor-in-chief` → `Editor-in-Chief` (`in` is a stopword in both styles).
606
- The compound's first part always capitalizes when the compound opens the title, and its last part always capitalizes when the compound closes the title — e.g. `the new state-of-the-art` → `The New State-of-the-Art` (changed from the original simplification by product decision during execution, 2026-07-27).
607
- - A word already in ALL-CAPS (2+ letters, e.g. an acronym like `API`) is left exactly as written.
608
- - **AP** (default) lowercases articles (`a`, `an`, `the`), coordinating conjunctions (`and`, `but`, `or`, `nor`, `for`, `so`, `yet`), and prepositions of **3 letters or fewer** (`at`, `by`, `in`, `of`, `off`, `on`, `out`, `to`, `up`, `via`).
609
- - **Chicago** lowercases the same articles/conjunctions, plus **every** preposition regardless of length (the short ones above, plus `about`, `above`, `across`, `after`, `against`, `along`, `among`, `around`, `before`, `behind`, `below`, `between`, `during`, `through`, `toward`, `under`, `until`, `with`, `within`, `without`) —
610
- e.g. Chicago lowercases `'...walking through the park'` → `'...walking through the Park'`, where AP capitalizes `Through`.
611
-
612
- **`$sentence`** — only the first word is capitalized; every other word is lowercased unless it's an `exceptions` entry (as-written) or already ALL-CAPS (left alone).
613
-
614
- **Word position counts a phrase exception as one word.**
615
- A *phrase* exception (one containing whitespace or a dot, like `Node.js` or `VS Code` — see the phrase-matching note above) is a single atomic token in the word sequence the `$`-styles case: it's emitted in its exact as-written form, and it **occupies a position**, so it never changes which word counts as first or last.
616
- With `exceptions: [VS Code]`, the already-correctly-cased heading `## VS Code actions for teams` produces no finding under `$sentence` (`actions` is the second word, not the first), and `## a guide to Node.js` becomes `## A Guide to Node.js` under `$title`/AP (`Node.js` is the last word, so `to` is a mid-title stopword and stays lowercase).
617
- Single-word exceptions (e.g. `GitHub`) behave as they always have — resolved by lookup rather than position.
618
-
619
- This used to be a bug, tracked as [Redocly/redocly#25610](https://github.com/Redocly/redocly/issues/25610) and fixed since:
620
- phrase exceptions were previously *masked out* of the text before word position was computed, which made a leading phrase promote the next word to sentence-initial under `$sentence`
621
- (`## VS Code actions for teams` was flagged, and under `fix: true` rewritten to `## VS Code Actions for teams`)
622
- and made a trailing phrase promote the preceding word to last-word position under `$title` (`a guide to Node.js` → `A Guide To Node.js`).
623
- If you had worked around it by rephrasing headings or by swapping in a custom regex `match`, neither is needed any more.
624
- See `rules/scope/title-case.ts`'s `recaseWords` for the tokenization that replaced the masking.
625
-
626
- **`$lower`** / **`$upper`** — the whole segment must be all-lowercase / all-uppercase respectively; no exceptions/ALL-CAPS carve-out (unconditional, matching Vale's own `$lower`/`$upper`).
627
-
628
- **Custom regex** — the whole segment text must satisfy the pattern.
629
- **Detection-only**: unlike the four `$`-styles, a failing regex is flagged but never auto-fixed, even though the rule itself is registered fixable.
630
- Like `pattern`'s `tokens`, an invalid regex is caught and silently produces zero problems rather than crashing the run.
631
-
632
- **Inline code is frozen.**
633
- A backtick-delimited span in the segment text (e.g. a heading like ``'the `configFile` option'``) is treated like an exception: its content is never flagged or rewritten by any of the four `$`-styles, even if it would otherwise land on the first/last word.
634
-
635
- **Fixable** for `$title`/`$sentence`/`$lower`/`$upper` only, one segment-wide edit per flagged segment.
636
- A **multi-line** segment (e.g. a soft-wrapped paragraph) is skipped entirely under these four styles — neither a problem nor a fix — since a `Fix` can only rewrite a single line; a custom regex `match` has no such restriction and still checks (and reports) multi-line segments, since it never produces a fix regardless of segment span.
637
-
638
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the segment's own text (first line only), 2nd = the `match` value itself** (e.g. `'$title'`, or the literal regex source for custom-regex mode), e.g. `'"%s" should use %s capitalization.'` → `'"the great escape" should use $title capitalization.'`.
639
-
640
- #### Metric Assertions (`metric`)
641
- Scores the document's prose with one of six published readability formulas, or measures its size, and flags the file once when the value falls outside `[min, max]`.
642
-
643
- ```yaml
644
- assertions:
645
- metric:
646
- formula: flesch-reading-ease
647
- min: 30
648
- ```
649
-
650
- The size formulas put a budget on a class of files, such as agent skills or pitches, so a page that grows past what a reviewer can read fails CI.
651
- They count the same prose the readability formulas score, so code blocks, front matter, and headings never count, and the `words` column of `recheck --readability` is the number `word-count` gates on.
652
-
653
- ```yaml
654
- recheck/skill-length:
655
- severity: error
656
- message: 'Skill %s is %s; at most 1500 allowed.'
657
- appliesTo: ['.claude/skills/**/SKILL.md']
658
- assertions:
659
- metric:
660
- formula: word-count
661
- max: 1500
662
- ```
663
-
664
- | Option | Type | Required | Description |
665
- | --- | --- | --- | --- |
666
- | `formula` | `string` | Yes | A readability formula: `flesch-reading-ease`, `flesch-kincaid-grade`, `gunning-fog`, `smog`, `coleman-liau`, `automated-readability`. Or a size formula: `word-count`, `sentence-count`, `reading-time` (minutes, rounded to one decimal before the bounds check). |
667
- | `min` | `number` | At least one of `min`/`max` | Minimum acceptable score; a lower score is a violation. |
668
- | `max` | `number` | At least one of `min`/`max` | Maximum acceptable score; a higher score is a violation. |
669
- | `wordsPerMinute` | `number` | No | Reading speed for `reading-time` (default `200`). Setting it with another formula is a validation error. |
670
-
671
- Omitting both `min` and `max`, an unrecognized `formula`, a non-positive `wordsPerMinute`, or an unknown option key are all validation errors — a metric assertion with no bound can never report anything, and an unrecognized formula would otherwise reach the scoring engine's own exhaustive-switch failure at lint time instead of at config validation.
672
-
673
- **Always summary-scoped.**
674
- Unlike every other assertion above, `metric` does not honor a configurable `scope:` — readability is a property of the WHOLE document's prose, not something a selector could sensibly narrow (a readability score isn't meaningful for one paragraph in isolation the way an `occurrence` count is).
675
- Config validation forces every `metric` rule to `scope: summary`.
676
- Omit `scope` on a `metric` rule (or write `scope: summary` explicitly); configuring any other scope prints a warning (`metric is always summary-scoped; ignoring configured scope ...`) and applies `summary` behavior anyway.
677
- Text from overlapping segments (e.g. a list nested inside a blockquote) is deduplicated by source position, same as `consistency`/`conditional` above.
678
-
679
- **What the score reads.**
680
- The metric scores flowing prose the way standard readability tools do: `paragraph`, `list-item`, `blockquote`, `table.cell`, and `table.header` text counts; **headings are excluded**, and `code`, `frontmatter`, `html`, `comment`, `alt`, and `link` content is never counted.
681
- Every block that does not end in terminal punctuation ends a sentence — an unpunctuated list item is one sentence, not a fragment fused into its neighbors.
682
- Without that rule, a run of bullets scored as one enormous "sentence" and pushed Flesch reading ease far below zero; with it, scores line up with other readability tools within syllable-heuristic differences.
683
-
684
- **Non-prose stripping.**
685
- Before scoring, each segment's text also has Markdoc tag-marker spans (`{% tag attr="x" %}`, `{% /tag %}`, and the `{%- ... -%}` trim variant) and backtick-delimited inline code spans stripped out — neither is readable prose, and both otherwise skew word/syllable counts.
686
- Prose between two block-tag markers still counts (only the marker spans themselves are removed); a paragraph consisting only of tag markers contributes nothing.
687
- Multi-backtick delimiters (`` ``like this`` ``) are handled conservatively as a simple open-run/close-run pair match, not a full CommonMark-correct implementation.
688
-
689
- **Detection-only** (not fixable) — there is no single edit that would "fix" a readability score.
690
- Reports at most **one** problem per file, always at `line: 1, column: 1` (there is no single source position a whole-document score belongs to) — never divided by zero: a file with no prose at all (empty, or only code/frontmatter) is never flagged, regardless of `min`/`max`.
691
-
692
- The rule's `message` is substituted against up to **four** values, in this order: **1st = the formula name, 2nd = the computed score, 3rd = `min` (or `-∞` if unset), 4th = `max` (or `∞` if unset)** — e.g. the internal fallback `'Readability (%s) is %s; expected between %s and %s.'` → `'Readability (flesch-reading-ease) is 42.1; expected between 60 and ∞.'`.
693
- Size formulas use the fallback `'Document %s is %s; expected between %s and %s.'` → `'Document word-count is 2481; expected between -∞ and 2000.'`.
694
- The `message` validation cap is per-assertion: a `metric` rule's `message` may use up to **4** `%s` placeholders (one per value above), while every other assertion stays capped at 2.
695
- Fewer placeholders than values is fine — substitution is positional, so a 2-slot message receives the leading values (formula name, then score).
696
-
697
- <!-- recheck-disable-next-line no-gerund-headings -->
698
- #### Spelling Assertions (`spelling`)
699
- Vale-parity `spelling` check (detection-only): tokenizes each scoped segment's text into words and flags any word an [nspell](https://github.com/wooorm/nspell)/Hunspell speller doesn't recognize, with up to three suggested corrections.
700
-
701
- ```yaml
702
- assertions:
703
- spelling:
704
- vocab: [Redocly, Reunite]
705
- ignore: ['\bAcme\w*']
706
- ```
707
-
708
- | Option | Type | Required | Description |
709
- | --- | --- | --- | --- |
710
- | `dictionary` | `string` | No | Base path (WITHOUT the `.aff`/`.dic` extension) to a custom Hunspell dictionary pair, e.g. `dictionary: dict/custom` reads `dict/custom.aff` and `dict/custom.dic`. Resolved relative to `process.cwd()` (where the CLI is invoked from) unless absolute. Omit to use the bundled default English dictionary. |
711
- | `vocab` | `string[]` | No | Extra known-good words, matched case-insensitively; never flagged even when the speller itself doesn't recognize them (product names, jargon, etc.). Unioned with the [built-in technical proper-noun vocabulary](#built-in-technical-proper-noun-vocabulary) unless `builtinVocabulary: false`. |
712
- | `ignore` | `string[]` | No | Regex patterns; a token matching ANY of them is never flagged, e.g. `['\bAcme\w*']` to allow every inflection of a brand name. An invalid pattern is silently ignored, same convention as `pattern`'s `tokens`. |
713
- | `builtinVocabulary` | `boolean` | No | Default `true`. Whether [`TECHNICAL_PROPER_NOUNS`](#built-in-technical-proper-noun-vocabulary) is unioned into the accepted-word set alongside `vocab`. A multi-token entry (`Node.js`, `VS Code`) is split into its individual words, each accepted separately — correct for a per-word spell check, unlike `capitalization`'s whole-phrase matching. Set `false` for a closed vocabulary of only this rule's own `vocab`. |
714
-
715
- All options are optional — an empty `spelling: {}` is valid (default dictionary, no extra vocabulary, no ignore patterns, built-in vocabulary on).
716
- Unknown option keys, a non-string/empty-string `dictionary`, a `vocab`/`ignore` entry that isn't a non-empty string, or a non-boolean `builtinVocabulary` are all validation errors.
717
-
718
- **Optional peer dependencies — install to enable.**
719
- `nspell` and its default dictionary (`dictionary-en`) are **optional peer dependencies**: installing `@redocly/recheck` itself pulls in **neither**.
720
- Enable `spelling` with:
721
-
722
- ```bash
723
- npm i nspell dictionary-en
724
- ```
725
-
726
- ...or, if every `spelling` rule in your config sets its own `dictionary` path, you only need the speller itself (the bundled dictionary is never touched):
727
-
728
- ```bash
729
- npm i nspell
730
- ```
731
-
732
- If a config enables `spelling` without the required peer(s) installed, `recheck --validate-config` fails with an actionable error naming the exact command above — never a bare `Cannot find module 'nspell'` surfacing for the first time at lint time.
733
-
734
- **Dictionaries load lazily.**
735
- Neither `nspell` nor `dictionary-en` is imported unless some rule in your config actually has a `spelling` assertion — a config without one never touches either package, at either `validate` or lint time.
736
- The loaded speller (including the ~500KB parsed dictionary) is cached per dictionary source for the process's lifetime, so every file/rule sharing the same `dictionary` (or the shared default) reuses one instance rather than reloading it per call.
737
-
738
- **Word tokenization.**
739
- Words are matched with `/\p{L}+(?:['’]\p{L}+)?/gu` — Unicode letter runs, with an optional apostrophe-joined suffix so contractions (`don't`, `it's`) tokenize as one word.
740
- A token is skipped (never checked) when it's in `vocab` (case-insensitively), matches any `ignore` pattern, is ALL-CAPS (2+ letters, e.g. an acronym) — matching the same ALL-CAPS carve-out `$title`/`$sentence` capitalization use — or is digit-adjacent (see below).
741
- Because `\p{L}` can never match a digit, a token touching one is never captured WHOLE by the tokenizer in the first place: a digit-adjacent identifier like `config2` still splits into a letter-only fragment (`config`) as its own regex match.
742
- Rather than checking that fragment like any other word, a digit-adjacency guard looks at the character immediately before and after each match and skips it when either neighbor is a digit — so common digit-bearing identifiers (`sha256` → `sha`, `utf8` → `utf`, `oauth2` → `oauth`, `es6` → `es`, `log4j` → both `log` and `j`, `2fast` → `fast`) are no longer flagged as false-positive misspellings.
743
- This mitigates, but doesn't eliminate, every false positive from the tokenizer's inability to capture digits at all — a token entirely surrounded by non-digit characters is still checked normally, so a genuine misspelling elsewhere in the same sentence is still flagged.
744
-
745
- **Code is never spell-checked, by construction of scope segmentation — not something this assertion special-cases.**
746
- A fenced or indented code block is its own `scope: 'code'` segment, entirely distinct from `paragraph`/`heading`/etc.; scoping `spelling` to prose (the common case, e.g. `scope: paragraph` or an array of prose scopes) means `ctx.segments` never contains one.
747
- A backtick-delimited **inline** code span, though, remains embedded as raw text inside a prose segment's own content (verified directly against the extractor) — those spans are masked out before tokenizing, the same length-preserving technique `capitalization`'s backtick-span freezing uses, so positions of any remaining flagged word stay exact.
748
- Scoping `spelling` to `all`/`raw` (or leaving `scope` at its default) checks the whole raw file, literal code included — same default-scope behavior every other native assertion (`swap`, `pattern`, ...) has.
749
-
750
- **Detection-only** — no `fix`.
751
- The rule's `message` gets two positional `%s` substitutions, in this order: **1st = the unrecognized word, 2nd = a suggestion suffix** — either `''` (zero suggestions) or `' — did you mean: a, b, c?'` (one to three, comma-joined) — e.g. the internal fallback `'Unknown word "%s"%s'` → `'Unknown word "wrold" — did you mean: wold, world?'`.
752
-
753
- #### Built-in technical proper-noun vocabulary
754
-
755
- `capitalization` and `spelling` both ship a built-in list of common technical/product proper nouns — `TECHNICAL_PROPER_NOUNS`, exported from `@redocly/recheck`'s public API (`import { TECHNICAL_PROPER_NOUNS } from '@redocly/recheck'`) so you can read or extend it yourself.
756
- It exists so a config that turns on sentence-case headings or spelling doesn't immediately need to hand-list the same 15+ mixed-case technology names every project already has to deal with (`OpenAPI`, `npm`, `Node.js`, `VS Code`, ...).
757
-
758
- **On by default**, per rule:
759
- - `capitalization` unions it into `exceptions` (so a listed name keeps its as-written casing under every `$`-style, including `$sentence`).
760
- - `spelling` unions it into `vocab` (so those words are never reported as misspellings), splitting any multi-token entry into its individual words first — a per-word spell check has no way to accept a whole phrase atomically the way `capitalization`'s phrase matching does.
761
- - Either union is opted out of independently with that rule's own `builtinVocabulary: false`, restoring strict pre-built-in behavior (a closed vocabulary of only what you list yourself).
762
- - Your own `exceptions`/`vocab` on the same rule **compose** with the built-ins rather than replacing them — unlike a preset-shipped list on the same rule key, which a same-key override *would* replace entirely (see [`extends` presets](#extends-presets) above).
763
- This is exactly how [`recheck/prose`](#extends-presets)'s `capitalization` rule gets its protection for common technical nouns without shipping any `exceptions` of its own.
764
-
765
- **Multi-token entries work.**
766
- An entry containing a dot or whitespace (`Node.js`, `VS Code`, `Visual Studio Code`, `GitHub Actions`, `Google Cloud`, `Azure DevOps`) is matched by `capitalization` as a whole phrase against the segment text (longest-match-first, case-insensitive but otherwise literal) and preserved verbatim — not looked up per word, which is what a single-token entry like `GitHub` still gets.
767
-
768
- **Inclusion bar** (why an entry is — or isn't — in the list, and the bar to clear before proposing one): an entry qualifies if it's an unambiguous technology, product, or company name whose exception listing wouldn't *weaken* capitalization/spelling checks — concretely, its lowercase form must not be a legitimate English word in its own right.
769
- That covers ordinary Title-Case brand names (`Android`, `Kubernetes`, `Redocly`) just as much as entries with an internal capital (`OpenAPI`, `GraphQL`), a dot (`Node.js`), or forced lowercase (`npm`) — `$sentence` lowercases every non-first word regardless of how "ordinary" its casing looks, so plain Title-Case names need protection too.
770
- Excluded, deliberately:
771
- - **Pure ALL-CAPS acronyms** (`JWT`, `YAML`) — already handled structurally by the ALL-CAPS carve-out both `capitalization` and `spelling` apply, so listing them adds maintenance for no behavior change.
772
- Note this is narrower than "looks like an acronym": `OAuth` and `AsyncAPI` are mixed-case, not pure ALL-CAPS, and are in the list.
773
- - **Terms with legitimate lowercase prose usage** — generic English (`cloud`, `apps`),
774
- words that are ALSO ordinary English words even though they're Redocly product names too (`Realm`, `Replay`, `Respect` — listing them would force-capitalize ordinary usage like "we respect your privacy"; `Node` — the common technical noun, superseded by the `Node.js` phrase entry for the platform specifically),
775
- and — caught by a later audit, not the original pass — ordinary brand-shaped words with a real dictionary meaning (`Chrome`, `Markdown`, `Postman`, `Prettier`, `Safari`, `Swagger`, `Windows`; see `src/data/proper-nouns.ts`'s header for each one's disqualifying lowercase usage).
776
- A few real dictionary words (`Android`, `Docker`, `TypeScript`) were judged rare enough in ordinary lowercase usage to keep anyway — a documented, deliberate risk-acceptance, not an oversight.
777
- List your own such names in your rule's own `exceptions`/`vocab`, which compose with this list as described above.
778
-
779
- Two automated tests in `src/data/__tests__/proper-nouns.test.ts` enforce this:
780
- one checks every entry's shape against the bar above — no pure ALL-CAPS, and, mechanically, no single-token entry whose lowercase form the REAL spelling dictionary (`dictionary-en`/`nspell`, the same pair `spelling` loads at runtime) accepts as a legitimate English word, unless it's named in an explicit accepted-risk allowlist —
781
- plus alphabetization and no duplicates.
782
- A round-trip guard separately drives every entry through the real `capitalization` and `spelling` rules and fails the suite if any entry can't actually be protected — the list can't silently regress into decoration.
783
-
784
- #### Length Assertions (`length`)
785
- Recheck-original, detection-only check: measures each scoped segment's size — in characters, words, or sentences — and flags a segment whose measurement falls outside `[min, max]`.
786
- Unlike `metric` (always whole-document), `length` honors whatever `scope` the rule configures — e.g. `scope: alt` to cap image alt text, or `scope: sentence` to cap sentence length in words.
787
- [`recheck/google`](#extends-presets) ships this for the guide's stated "fewer than 26 words per sentence" limit (`google/sentence-length`); Microsoft's 150-character alt-text cap is the other published example of this shape.
788
-
789
- ```yaml
790
- assertions:
791
- length:
792
- unit: characters
793
- max: 150
794
- ```
795
-
796
- | Option | Type | Required | Description |
797
- | --- | --- | --- | --- |
798
- | `unit` | `'characters' \| 'words' \| 'sentences'` | Yes | What `min`/`max` count: raw character length, whitespace-delimited words (the same tokenizer `metric` uses for its own word counts — see `metrics/statistics.ts`'s `tokenizeWords`), or sentences via the shared `splitSentences` sentence-boundary logic (`scopes/sentences.ts`). |
799
- | `min` | `number` | At least one of `min`/`max` | Minimum allowed size; a smaller segment is a violation. |
800
- | `max` | `number` | At least one of `min`/`max` | Maximum allowed size; a larger segment is a violation. |
801
-
802
- Omitting both `min` and `max`, a missing/unrecognized `unit`, or an unknown option key are all validation errors — same reasoning as `occurrence`/`metric` above.
803
- An inverted range (`min` > `max`) is also an error.
804
-
805
- **Detection-only** (not fixable) — there is no single edit that would resize a segment to fit.
806
- Reports at most one problem per flagged segment, at the segment's own `startLine`/`startColumn`.
807
-
808
- The rule's `message` gets three positional `%s` substitutions, in this order: **1st = the segment's measured size, 2nd = the unit name, 3rd = the bound that was violated** (`min` or `max`, whichever applied), e.g. the internal fallback `'Segment is %s %s; at most %s allowed'` → `'Segment is 151 characters; at most 150 allowed'`.
809
- The message validation cap for `length` is **3** placeholders (one per value above), same reasoning as `metric`'s 4-cap.
810
-
811
- #### Built-in Prose Assertions
812
- Beyond `swap`, `pattern`, `occurrence`, `repetition`, `consistency`, `conditional`, `capitalization`, `metric`, `spelling`, and `length` above, Recheck ships a small set of native prose/format checks:
813
- - `semantic-line-breaks` - Semantic line break validation ✅ **Fixable**
814
- - `max-image-size` - Oversized image detection
815
-
816
- Three of the assertions above (`repetition`, `consistency`, `capitalization`) are bundled, pre-configured, in the [`recheck/prose`](#extends-presets) preset,
817
- and `capitalization`/`length` are also used by [`recheck/google`](#extends-presets) (sentence-case headings and list items, and a sentence-length cap);
818
- the remaining four (`occurrence`, `conditional`, `metric`, `spelling`) are documented [opt-ins](#opt-in-prose-assertions) with copy-paste snippets, not shipped in any preset by default.
819
-
820
- ### Recheck-original structural rules
821
-
822
- Seven rules have no markdownlint counterpart, so they sit outside the 53-rule parity set
823
- (and outside the parity comparison).
824
- All seven are **detection-only** (`fix: false`).
825
- The
826
- canonical list is `RECHECK_ORIGINAL_TOKEN_RULE_NAMES` in `src/rules/token/index.ts`.
827
-
828
- The table below covers five of them.
829
- The other two — `markdoc-unknown-tag` and
830
- `markdoc-attributes` — need a tag schema to check anything, so they are documented with
831
- the [`recheck/markdoc`](#extends-presets) preset instead.
832
-
833
- | Rule | Flags | Why |
834
- | --- | --- | --- |
835
- | `no-empty-headings` | A heading whose text content is empty (a bare `#`, or markup that renders to nothing such as `## <span></span>`) | An empty heading still lands in the document outline and in screen-reader heading navigation. Inline code counts as content, so `` # `config.yaml` `` is fine. |
836
- | `no-duplicate-link-destinations` | The second and later links to one destination when the link **text** differs from the first occurrence's | Screen-reader users listing a page's links hear one target described inconsistently; the texts also drift apart over time. Repeating the *same* text for the same destination is ordinary prose and is not flagged. Resolves reference links through their definition. |
837
- | `list-length` | A list (ordered or unordered) with fewer than `min` items (default 2) or more than `max` items (no default — unbounded unless set) | A single-item list usually reads better as a plain sentence, and a very long list asks readers to hold too many parallel items in mind. Every list is evaluated independently, including nested sublists — a short sublist is flagged even when its parent list is long enough. |
838
- | `markdoc-syntax` | A grammar-level Markdoc tag error — a malformed span, an unquoted "bareword" attribute/primary value, or a close tag carrying attributes | These are invalid under real Markdoc's own grammar regardless of any tag schema, so the rule fires on custom/unknown tags and under `schema: false` alike. See the [`recheck/markdoc`](#extends-presets) preset bullet below for the full behavior and a config example. |
839
- | `markdoc-pairing` | An unclosed, orphaned, or interleaved (crossed) Markdoc tag pair, or a schema-declared self-closing tag written with a close it must not have | Same grammar-level scope as `markdoc-syntax` — see the [`recheck/markdoc`](#extends-presets) preset bullet below. |
840
-
841
- The first three rules are **opt-in — not shipped in any preset**; configure them
842
- individually as shown below.
843
-
844
- `markdoc-syntax` and `markdoc-pairing` work the other way around: they ship only inside
845
- the [`recheck/markdoc`](#extends-presets) preset, and both need `markdoc: true` (or the
846
- object form) to ever see a Markdoc tag token.
847
- Naming either rule key on its own, without
848
- the flag, validates but can never report anything — and you get no warning about it,
849
- because the stale-config warning fires on `extends` containing `"recheck/markdoc"`
850
- (`warnStaleMarkdocPreset` in `config/validate.ts`), not on individual rule keys.
851
- The
852
- preset bullet below covers both rules' full behavior with the flag on, plus a config
853
- example.
854
-
855
- ```yaml
856
- recheck/empty-headings:
857
- severity: error
858
- message: 'Headings should have text content.'
859
- assertions:
860
- no-empty-headings: {}
861
-
862
- recheck/link-text-consistency:
863
- severity: warn
864
- message: 'Link destination "%s" is already linked by different text.'
865
- assertions:
866
- no-duplicate-link-destinations: {}
867
-
868
- recheck/list-length:
869
- severity: warn
870
- message: 'List has %s item(s).'
871
- assertions:
872
- list-length: { min: 2, max: 10 }
873
- ```
874
-
875
- For markdown structure/format rules (headings, lists, links, tables, whitespace, and 49 more), see [Markdownlint parity](#markdownlint-parity) below — `no-trailing-spaces`, `no-hard-tabs`, `line-length`, `ul-style` (bullet style), `no-duplicate-heading`, and `link-fragments` are all part of that 53-rule set, not this native list.
876
- (`max-line-length`, `bullet-style`, `no-duplicate-headings`, and `no-broken-fragment-links` were pre-parity native ids for those same rules; they were removed rather than kept as aliases — see [Migrate from markdownlint](#migrate-from-markdownlint).)
877
-
878
- ### Enhanced Scope Support
879
-
880
- The `scope` field supports a string, an array (OR'd together), and a `~negation` / `&`-conjunction
881
- selector syntax:
882
- ```yaml
883
- scope: all # Apply to all content (default)
884
- scope: raw # Apply to raw file content, bypassing scope segmentation
885
- scope: summary # Apply to the document's prose: paragraph, heading, list-item, blockquote, and table-cell text (alias: default)
886
- scope: sentence # Apply to sentences only
887
- scope: paragraph # Apply to paragraphs only
888
- scope: heading # Apply to all headings
889
- scope: code # Apply to code blocks only
890
- scope: list-item # Apply to list item text
891
- scope: blockquote # Apply to blockquote text
892
- scope: table.header # Apply to table header cells
893
- scope: table.cell # Apply to table body cells
894
- scope: markdoc.tag # Apply to Markdoc tag spans (`{% ... %}`), requires markdoc: true
895
- scope: frontmatter # Apply to YAML frontmatter
896
- scope: html # Apply to raw HTML blocks
897
- scope: comment # Apply to HTML comments
898
- scope: alt # Apply to image alt text
899
- scope: link # Apply to link text
900
- scope: # Apply to specific heading levels
901
- - heading.h1
902
- - heading.h2
903
- - heading.h3
904
- scope: # Selector syntax: '~' negates, '&' conjoins
905
- - "~blockquote & ~heading"
906
- ```
907
-
908
- ### Markdoc-aware linting (`markdoc: true`)
909
-
910
- Opt-in — off by default, since Liquid/Jinja templates use the same `{% %}` delimiters and would otherwise get mistokenized as Markdoc:
911
-
912
- ```yaml
913
- markdoc: true # shorthand for `{ schema: 'realm' }`
914
- ```
915
-
916
- Writing *about* Markdoc syntax rather than using it (docs like this one, a tutorial, a changelog entry)?
917
- Wrap the literal `{% ... %}` in a code span — `` `{% partial /%}` `` — instead of leaving it bare in prose.
918
- Code spans never tokenize as Markdoc tags whether the flag is on or off, so that's the escape hatch.
919
-
920
- #### Object form: choosing or extending the tag schema
921
-
922
- `markdoc: true` is shorthand for the common case.
923
- The object form adds two things the
924
- boolean can't express: turning the schema-aware checks off while keeping tag tokenization,
925
- and layering a project's own custom tags over the built-in schema.
926
-
927
- ```yaml
928
- markdoc:
929
- schema: realm # required -- 'realm' (the built-in schema below) or `false`; there is no default if this key is omitted
930
- extend: # optional: your own tags, merged over the base schema
931
- tags:
932
- myCustomTag:
933
- selfClosing: true
934
- attributes:
935
- level:
936
- type: string
937
- enum: [info, warning, danger]
938
- required: true
939
- ```
940
-
941
- - **`schema: realm`** — the same built-in schema `markdoc: true` uses: `@markdoc/markdoc`'s
942
- own built-in tags composed with `@redocly/theme`'s tag definitions.
943
- It's generated from a
944
- theme build rather than hand-written, and a test fails if it drifts out of sync (see
945
- [CONTRIBUTING.md](CONTRIBUTING.md) for the regeneration command).
946
- This is what most
947
- projects want, and what the four [`recheck/markdoc`](#extends-presets) rules validate
948
- against by default.
949
- - **`schema: false`** — tokenization and tag **pairing** still run, so `markdoc.tag` scope,
950
- prose-scope exclusion, fix protection, and `markdoc-syntax`/`markdoc-pairing`'s
951
- grammar-level checks all still work.
952
- Only the two schema-dependent rules
953
- (`markdoc-unknown-tag`, `markdoc-attributes`) go inert, since there's no schema left for
954
- "unknown tag" or "missing required attribute" to mean anything against.
955
- Use this if you
956
- write Markdoc tags but don't have (or don't want) a schema to validate them against.
957
- - **`extend.tags`** — merges your own tag definitions over the base schema.
958
- On a name
959
- collision the merge is a **whole-tag replace**, matching how Markdoc's own config
960
- composition works, not a per-attribute deep merge.
961
- Declare your project's custom tags
962
- here (for example, a docs site's own `@theme/markdoc/schema.ts` overrides) so
963
- `markdoc-unknown-tag` and `markdoc-attributes` validate against your real tag surface
964
- instead of flagging every custom tag as unknown.
965
- Under `schema: false` there is no base
966
- to merge over, so `extend` does nothing.
967
- - **`extend.tagsFile`** — the same tag-definition surface as `extend.tags`, but sourced from
968
- a separate YAML file instead of written inline into `recheck.yaml`.
969
- This is the shape
970
- [`recheck --generate-markdoc-schema`](#generate-a-tagsfile-recheck---generate-markdoc-schema) below generates,
971
- so a project with tags defined in TypeScript (a `@theme/markdoc/schema.ts` module, say)
972
- never hand-transcribes them into YAML.
973
- ```yaml
974
- markdoc:
975
- schema: realm
976
- extend:
977
- tagsFile: ./recheck-markdoc-tags.yaml
978
- ```
979
- - **Resolution**: the path is resolved relative to the directory containing the
980
- `recheck.yaml`/`recheck.yml` that names it — never the process's current working
981
- directory — so `tagsFile: ./tags.yaml` always reads the file next to that config,
982
- wherever `recheck` is invoked from.
983
- - **Precedence**: tags merge in the order built-in schema → `tagsFile` → inline
984
- `extend.tags`, each layer a whole-tag replace on a name collision (same rule as
985
- `extend.tags` above).
986
- `tags` and `tagsFile` can both be set on the same `extend` block;
987
- `extend` with neither key is rejected by config validation as a likely no-op.
988
- - **Errors are fatal to the whole run, not a silent markdoc downgrade.**
989
- A `tagsFile` that
990
- doesn't exist, isn't valid YAML, isn't a YAML map, or contains a tag entry with an
991
- invalid shape all fail `recheck`/`recheck --validate-config` outright
992
- (`Configuration validation failed!`, the same failure every other structurally-invalid
993
- config produces) — markdoc checking is never quietly switched off while the rest of the
994
- config keeps running.
995
-
996
- Turning `markdoc` on (either form) changes how every prose rule sees a Markdoc tag, not just `markdoc.tag` (above):
997
-
998
- - **Prose scopes exclude the tag itself.**
999
- `paragraph`, `heading`, `list-item`, `blockquote`, and `table.header`/`table.cell` all blank a tag's own `{% ... %}` span out of their content before any rule runs — a `swap`/`pattern`/`capitalization` match can't fire on the tag's syntax, and a `length`/`metric` count doesn't include it.
1000
- The blanking is position-preserving (same-width spaces, never a deletion), so real text on either side of a tag keeps its exact line and column.
1001
- - **A segment with no prose left isn't emitted at all.**
1002
- A heading or table cell whose entire text IS a tag (`# {% #anchor %}`) produces no `heading.h1`/`table.cell` segment — there's nothing for a heading or cell rule to check, so none fires on it.
1003
- - **`--fix` never rewrites a Markdoc tag's bytes.**
1004
- Every proposed fix is checked against the document's tag spans before it's applied:
1005
- one that doesn't touch a tag goes through untouched, one that fully covers a tag with a same-length replacement gets the tag spliced back in,
1006
- and anything that would change a tag's length or split it in half is withheld instead — a withheld fix is reported (`skippedFixes` in the [Library API](#library-api)), not silently swallowed.
1007
- - **Two CommonMark constructs Markdoc doesn't have stop being recognized.**
1008
- Markdoc's own tokenizer disables indented code blocks and setext headings (the `Title\n===\n` underline form) unconditionally, which is how Realm renders, so `markdoc: true` disables them too — and only while the flag is on:
1009
- - A 4+-space-indented block that would otherwise be an indented code block parses as ordinary content instead: a paragraph, list, or fence, whichever the un-indented text would have produced.
1010
- This shows up most with a block-positioned tag followed immediately by more indented lines, and with genuinely indented example text.
1011
- Realm renders both as prose, so matching that is the intent.
1012
- - A text line immediately followed by a `---`/`===` line, with no blank line between, no longer forms a heading.
1013
- This also fixes a common false positive: a tag on its own line (`{% table %}`, say) directly followed by a `---` line is ordinary Markdoc table-row syntax, but without the flag it reads as a setext heading whose text is the tag itself.
1014
- With the flag on, the tag is its own token and can't merge into a paragraph that `---` would complete.
1015
- - Practical effect: on documents using either construct, expect `heading-style`, `blanks-around-headings`, `capitalization`, and `code-block-style` findings to shift when you first turn the flag on.
1016
- They are moving to match how Markdoc actually renders, not regressing.
1017
-
1018
- ### Generate a tagsFile: `recheck --generate-markdoc-schema`
1019
-
1020
- Projects that define their own Markdoc tags in TypeScript — a `@theme/markdoc/schema.ts`
1021
- module exporting a `tags` map, the shape both `docs/realm` and `docs/intranet` use in this
1022
- monorepo — can generate an `extend.tagsFile` YAML file from it instead of hand-transcribing
1023
- each tag's schema:
1024
-
1025
- ```bash
1026
- recheck --generate-markdoc-schema --from path/to/schema.ts --out recheck-markdoc-tags.yaml
1027
- ```
1028
-
1029
- - **`--from <path>`** (repeatable) — a project schema module to extract tags from, resolved
1030
- relative to the current working directory.
1031
- The module must export `tags` (named or on a
1032
- `default` object) mapping tag name to a Markdoc tag config; only the statically-checkable
1033
- facets (`selfClosing`, and each attribute's `type`/`required`/`default`/`enum`) are
1034
- extracted — anything richer (a custom attribute class, a `validate()` function) is written
1035
- out as `dynamic: true`, the same reduction the built-in `realm` schema goes through.
1036
- Pass
1037
- `--from` more than once to merge several modules; an identical tag definition repeated
1038
- across modules is fine, but two modules disagreeing about the same tag's shape fails the
1039
- command rather than letting flag order silently pick one.
1040
- - **`--out <path>`** — where to write the generated YAML, resolved relative to the current
1041
- working directory.
1042
- The file opens with a generated-file header naming its source module(s)
1043
- and the exact command to regenerate it.
1044
- - **`--check`** — verifies the output file matches what a fresh generation would produce,
1045
- without writing it: exits `0` and prints `<path> is up to date.` when it matches, exits `1`
1046
- and prints a one-line diagnosis (file missing, or stale) otherwise.
1047
- This is what a CI drift
1048
- check should call — see this repo's own wiring below.
1049
-
1050
- **TypeScript sources need a loader.**
1051
- `recheck --generate-markdoc-schema` dynamic-`import()`s each
1052
- `--from` module directly; running the command under plain `node` against a `.ts` module
1053
- fails with an actionable one-line error naming the fix, rather than a raw stack trace:
1054
-
1055
- ```text
1056
- could not import "path/to/schema.ts" — TypeScript sources need a loader, e.g.: pnpm exec tsx
1057
- node_modules/.bin/recheck --generate-markdoc-schema … (Cannot find module '<a module your schema
1058
- imports>' imported from '<path to your schema.ts>')
1059
- ```
1060
-
1061
- (wrapped above for line length; the real message is one line.
1062
- The parenthetical is Node's
1063
- own error and its shape varies: for a schema whose extensionless internal imports plain
1064
- `node` cannot resolve — the common case — the "imported from" path is your schema file
1065
- itself; for a `--from` path that doesn't exist at all it is recheck's own command module.)
1066
-
1067
- Run it through `tsx` instead (directly, or via a package script that already wraps it, like
1068
- this repo's `recheck:markdoc-tags` below) — a plain `.js` schema module needs no loader and
1069
- works under either.
1070
-
1071
- **Experimental, pending a canonical manifest.**
1072
- This command is an interim bridge, not a
1073
- long-term source of truth: [issue #25666](https://github.com/Redocly/redocly/issues/25666)
1074
- tracks Realm itself emitting one canonical, statics-only Markdoc tag/schema manifest, which
1075
- would let this generator (and its drift check) retire in favor of reading that manifest
1076
- directly.
1077
- Until then, `recheck --generate-markdoc-schema` is the supported way to keep a project's
1078
- `tagsFile` in sync with its real tag schema modules.
1079
-
1080
- #### Worked example: this repo's own setup
1081
-
1082
- This monorepo's root `recheck.yaml` uses `extend.tagsFile` to pull in the custom tags from
1083
- both `docs/realm` and `docs/intranet`'s own `@theme/markdoc/schema.ts` modules:
1084
-
1085
- ```yaml
1086
- markdoc:
1087
- schema: realm
1088
- extend:
1089
- tagsFile: ./recheck-markdoc-tags.yaml
1090
- ```
1091
-
1092
- The committed `recheck-markdoc-tags.yaml` is generated, not hand-written — its header names
1093
- the exact regenerate command:
1094
-
1095
- ```yaml
1096
- # Generated file — do not hand-edit.
1097
- # Source module(s): ../../docs/realm/@theme/markdoc/schema.ts, ../../docs/intranet/@theme/markdoc/schema.ts
1098
- # Regenerate: recheck --generate-markdoc-schema --from ../../docs/realm/@theme/markdoc/schema.ts --from ../../docs/intranet/@theme/markdoc/schema.ts --out ../../recheck-markdoc-tags.yaml
1099
- ```
1100
-
1101
- The root `package.json` wraps that same invocation in one script, run from
1102
- `packages/recheck` via `tsx` (the schema modules are TypeScript source, see above):
1103
-
1104
- ```bash
1105
- pnpm run recheck:markdoc-tags # regenerate recheck-markdoc-tags.yaml
1106
- pnpm run recheck:markdoc-tags --check # verify it's current; exits 1 on drift
1107
- ```
1108
-
1109
- **Do not add `--` before `--check`.**
1110
- `pnpm run recheck:markdoc-tags -- --check` looks
1111
- equivalent but isn't: the script itself already ends in `pnpm --filter @redocly/recheck exec
1112
- tsx dist/cli.js --generate-markdoc-schema …`, and pnpm's own `--` forwarding through that nested `exec`
1113
- makes yargs read `--check` as a positional argument instead of the `--check` flag — the
1114
- command then silently regenerates the file and always exits `0`, defeating the whole point
1115
- of a drift check.
1116
- Always call it as `pnpm run recheck:markdoc-tags --check`, with no extra
1117
- `--`.
1118
-
1119
- CI runs exactly that check on every PR, as its own step in
1120
- `.github/workflows/recheck.yml` (after the package is built):
1121
-
1122
- ```yaml
1123
- - name: Markdoc tags file is current
1124
- run: pnpm run recheck:markdoc-tags --check
1125
- ```
1126
-
1127
- If it fails, regenerate locally with `pnpm run recheck:markdoc-tags` and commit the result.
1128
-
1129
- ### Rule Severity Levels
1130
-
1131
- Rules can be configured with different severity levels:
1132
-
1133
- - **`off`**: Disable the rule completely
1134
- - **`info`**: Informational messages (exit code 0)
1135
- - **`warn`**: Warning messages (exit code 0)
1136
- - **`error`**: Error messages (exit code 1)
1137
-
1138
- ### Auto-Fix Safety
1139
-
1140
- Fixability is declared by each assertion, not by config — a rule can be automatically corrected if and only if its assertion implements a `fix()`.
1141
- The `autoFixable` config key was removed — rules declare fixability; use `fix: false` to opt out.
1142
- A config that still sets `autoFixable` now fails validation with an unknown-property error.
1143
- To opt a rule out of auto-fixing, set `fix: false` on it instead.
1144
-
1145
- The `enabled` config key was likewise removed — it was schema-legal but never actually consulted by the engine (use `severity: off` to disable a rule).
1146
- A config that still sets `enabled` now fails validation with an unknown-property error.
1147
-
1148
- - ✅ **Fixable native assertions**: `swap`, `semantic-line-breaks`, `repetition`, `consistency`, `capitalization` (except its custom-regex `match` mode, which is always detection-only)
1149
- - ❌ **Not fixable native assertions**: `pattern`, `max-image-size`, `occurrence`, `conditional`, `metric`, `spelling`
1150
- - Of the 53 markdownlint-parity rules, 33 are fixable — see the [rule table](#markdownlint-parity) for the full per-rule breakdown (includes `no-trailing-spaces`, `no-hard-tabs`, `ul-style`, and more).
1151
-
1152
- ## Markdownlint parity
1153
-
1154
- Recheck ports all 53 of [markdownlint](https://github.com/DavidAnson/markdownlint)'s built-in rules (MD001-MD060, minus retired ids) as native `assertions`, verified against upstream by a differential parity harness (see [Parity with markdownlint](#parity-with-markdownlint) below).
1155
- Enable the full set with one line:
1156
-
1157
- ```yaml
1158
- extends: [recheck/markdown]
1159
- ```
1160
-
1161
- Each rule is available as its own `recheck/<name>` assertion id, so you can also enable a subset directly:
1162
-
1163
- ```yaml
1164
- recheck/heading-increment:
1165
- severity: error
1166
- message: 'Heading levels should only increment by one level at a time.'
1167
- assertions:
1168
- heading-increment: {}
1169
- ```
1170
-
1171
- | Rule name | MD id | Fixable |
1172
- | --- | --- | --- |
1173
- | `heading-increment` | MD001 | No |
1174
- | `heading-style` | MD003 | No |
1175
- | `ul-style` | MD004 | Yes |
1176
- | `list-indent` | MD005 | Yes |
1177
- | `ul-indent` | MD007 | Yes |
1178
- | `no-trailing-spaces` | MD009 | Yes |
1179
- | `no-hard-tabs` | MD010 | Yes |
1180
- | `no-reversed-links` | MD011 | Yes |
1181
- | `no-multiple-blanks` | MD012 | Yes |
1182
- | `line-length` | MD013 | No |
1183
- | `commands-show-output` | MD014 | Yes |
1184
- | `no-missing-space-atx` | MD018 | Yes |
1185
- | `no-multiple-space-atx` | MD019 | Yes |
1186
- | `no-missing-space-closed-atx` | MD020 | Yes |
1187
- | `no-multiple-space-closed-atx` | MD021 | Yes |
1188
- | `blanks-around-headings` | MD022 | Yes |
1189
- | `heading-start-left` | MD023 | Yes |
1190
- | `no-duplicate-heading` | MD024 | No |
1191
- | `single-h1` | MD025 | No |
1192
- | `no-trailing-punctuation` | MD026 | Yes |
1193
- | `no-multiple-space-blockquote` | MD027 | Yes |
1194
- | `no-blanks-blockquote` | MD028 | No |
1195
- | `ol-prefix` | MD029 | Yes |
1196
- | `list-marker-space` | MD030 | Yes |
1197
- | `blanks-around-fences` | MD031 | Yes |
1198
- | `blanks-around-lists` | MD032 | Yes |
1199
- | `no-inline-html` | MD033 | No |
1200
- | `no-bare-urls` | MD034 | Yes |
1201
- | `hr-style` | MD035 | No |
1202
- | `no-emphasis-as-heading` | MD036 | No |
1203
- | `no-space-in-emphasis` | MD037 | Yes |
1204
- | `no-space-in-code` | MD038 | Yes |
1205
- | `no-space-in-links` | MD039 | Yes |
1206
- | `fenced-code-language` | MD040 | No |
1207
- | `first-line-h1` | MD041 | No |
1208
- | `no-empty-links` | MD042 | No |
1209
- | `required-headings` | MD043 | No |
1210
- | `proper-names` | MD044 | Yes |
1211
- | `no-alt-text` | MD045 | No |
1212
- | `code-block-style` | MD046 | No |
1213
- | `single-trailing-newline` | MD047 | Yes |
1214
- | `code-fence-style` | MD048 | No |
1215
- | `emphasis-style` | MD049 | Yes |
1216
- | `strong-style` | MD050 | Yes |
1217
- | `link-fragments` | MD051 | Yes |
1218
- | `reference-links-images` | MD052 | No |
1219
- | `link-image-reference-definitions` | MD053 | Yes |
1220
- | `link-image-style` | MD054 | Yes |
1221
- | `table-pipe-style` | MD055 | No |
1222
- | `table-column-count` | MD056 | No |
1223
- | `blanks-around-tables` | MD058 | Yes |
1224
- | `descriptive-link-text` | MD059 | No |
1225
- | `table-column-style` | MD060 | Yes |
1226
-
1227
- *(53 rules, 33 fixable.
1228
- Generated from the built rule registry — `dist/rules/token/index.js`'s `allTokenRules`, cross-referenced against `benchmarks/parity/rule-map.mjs` for MD ids.)*
1229
-
1230
- ### `extends` presets
1231
-
1232
- Recheck ships nine built-in presets, referenced by id under `extends:`.
1233
- Presets are applied in listed order, then your own rule keys are merged on top — **your config always wins**: a rule key you define overrides the same key from a preset, and per-assertion options you set override just that assertion's preset options (other preset options for the same rule are preserved).
1234
-
1235
- There is one exception.
1236
- A rule may attach a milder severity to some of its own reports, and your config can't escalate those: `recheck/markdoc-attributes` reports unknown attributes at `warn` no matter what severity you give the rule (see the `recheck/markdoc` bullet below).
1237
- `severity: 'off'` still works as expected — it disables the rule entirely, so no reports of any severity.
1238
-
1239
- - **`recheck/markdown`** — the full 53-rule set from the table above, all at `severity: error` with upstream-faithful default options.
1240
- Equivalent to markdownlint's `{ default: true }`.
1241
- - **`recheck/markdown-relaxed`** — mirrors markdownlint's own `style/relaxed.json`: the same 53 rules, with `no-trailing-spaces`, `no-hard-tabs`, `no-multiple-blanks`, `no-multiple-space-blockquote`, `no-blanks-blockquote`, `line-length`, `ul-indent`, `no-inline-html`, `no-bare-urls`, `fenced-code-language`, and `first-line-h1` turned off.
1242
- - **`recheck/minimal`** — a small, high-signal set: `no-trailing-spaces`, `no-hard-tabs`, `single-trailing-newline`, `no-reversed-links`, `no-empty-links`.
1243
- - **`recheck/prose`** — Recheck's Vale-parity starter set, all at `severity: warn`: `repetition` (default options),
1244
- `consistency` (one US spelling enforced file-wide for `behavior`/`color`/`license`/`organize` vs. their British spellings, matched with `ignoreCase: true` so a capitalized, sentence-initial variant like `Colour` still counts),
1245
- and `capitalization` (`$sentence`, `scope: heading` only, `fix: false`, no preset-level `exceptions` — see below).
1246
- All three are scoped to prose segments — `repetition` and `consistency` to `summary` (the document's prose: paragraph, heading, list-item, blockquote, and table-cell text), `capitalization` to headings — so the preset never flags (and `--fix` never rewrites) code samples or frontmatter.
1247
- `extends: [recheck/markdown, recheck/prose]` is the one-liner that replaces a markdownlint + Vale combo.
1248
- See [Opt-in prose assertions](#opt-in-prose-assertions) below for three more prose assertions that exist but are deliberately **not** in this preset.
1249
- - **`recheck/markdoc`** — four Recheck-original rules that check Markdoc tag syntax itself (`{% tag attr="value" %}`) rather than prose or markdownlint parity.
1250
- All four are `fix: false`.
1251
- - `recheck/markdoc-syntax` (`error`) — malformed spans, unquoted "bareword" values, and close tags carrying attributes.
1252
- - `recheck/markdoc-pairing` (`error`) — unclosed, orphaned, or crossed tag pairs, and a self-closing tag written without a slash or given a close tag it shouldn't have.
1253
- - `recheck/markdoc-unknown-tag` (`warn`, because custom tags are common) — a tag name the schema doesn't declare.
1254
- - `recheck/markdoc-attributes` (mixed) — a missing required attribute, an enum or type violation, and a duplicate attribute report at `error`; an unknown attribute name, whether named or a stray positional value, always reports at `warn`.
1255
- That `warn` is set per report by the rule itself, so it wins over the rule's configured severity: setting the rule to `severity: error` does not escalate those reports.
1256
- Only `severity: 'off'` removes them, by disabling the rule.
1257
-
1258
- **These rules only fire when Markdoc tokenization is also on.**
1259
- Set `markdoc: true` (or the object form, see below) alongside `extends: [recheck/markdoc]`.
1260
- Extending the preset without the flag validates, but prints a console warning that the four rules can never report.
1261
- The flag stays an explicit opt-in because Liquid and Jinja templates use the same `{% %}` delimiters for unrelated syntax, so Recheck never assumes it.
1262
- - **`recheck/google`** — Google's developer documentation style guide (https://developers.google.com/style), CC BY 4.0, synced 2026-07-29.
1263
- 99 rules covering heading/list/table/link structure, sentence-case headings, sentence length, voice and contractions, plain language, product naming, compound word forms, and inclusive/precise-language terminology —
1264
- all derived from the *live* guide (see `packages/recheck/presets/google/PROVENANCE.md` for the rule -> source page -> quote -> verdict table, including everything considered and NOT shipped, and why).
1265
- `extends: [recheck/google]` is a one-line adoption of Google's style; combine with `recheck/markdown` for full structural linting too.
1266
- Rule ids are namespaced `google/<rule>` (not `recheck/<rule>`) so they never collide with the markdownlint-parity or other style-guide presets.
1267
- Structural/mechanical rules (heading hierarchy, list mechanics, alt-text presence, sentence length) are `severity: error`; every word-choice, terminology, and punctuation-convention rule is `severity: warn`.
1268
- See `packages/recheck/presets/google/sources.json` for the fetched-page hashes.
1269
- **Adopting this preset has a real, measured performance cost — roughly 2.7× the standard `recheck/markdown`-only profile's lint time on a docs-sized document set** — see [Performance](#performance) below (Phase 4) before turning it on in CI.
1270
- - **`recheck/microsoft`** — the Microsoft Writing Style Guide (https://learn.microsoft.com/en-us/style-guide/welcome/), CC BY 4.0 (via the guide's backing GitHub repository's LICENSE file — no `learn.microsoft.com` page states the licence itself, see `packages/recheck/presets/microsoft/PROVENANCE.md`), synced 2026-07-30.
1271
- 93 rules covering heading/list/table/alt-text structure, the guide's own numeric thresholds (paragraph length, list length, comma density, alt-text length), its signature "use contractions" rule, US spelling, bias-free and people-first terminology, and a large A-Z terminology word list —
1272
- all derived from the *live* guide and checked against four independent verification passes (~490 rules/entries across ~340 page fetches), with every Tier-1 pair anchored or demoted to detection-only wherever it was found capable of rewriting correct prose.
1273
- Rule ids are namespaced `microsoft/<rule>`.
1274
- Structural rules and the A-Z word list's three unconditional tiers are `severity: error`; voice, punctuation-convention, and UI-terminology rules are `severity: warn`.
1275
- Audience-conditional and UI-conditional entries (Microsoft's own "Tier 4") are never enforced, and developer-audience carve-outs relevant to API documentation (`header`, `context menu`, `disk`, `directory`) are excluded rather than misfiring on Redocly's own docs — see `packages/recheck/presets/microsoft/PROVENANCE.md` for the full table, every excluded candidate, and why.
1276
- Unlike `recheck/google` (which allows `click`), this preset bans all input-specific UI verbs (`click`, `press`, `hit`) in favor of `select` — the sharpest divergence between the two guides.
1277
- See `packages/recheck/presets/microsoft/sources.json` for the fetched-page hashes.
1278
- - **`recheck/inclusive-language`** — composable, guide-agnostic: the *intersection* of `recheck/google` and `recheck/microsoft`'s inclusive/bias-free/ableist/accessibility content —
1279
- terminology both flagship guides independently state should be avoided (`slave`, `master/slave`, `blacklist`/`whitelist`, `DMZ`, `grayed-out`, `he/she`, `normal person`/`healthy person`, `suffering from`/`victim of`, `differently abled`, `crippled`, `nuke`).
1280
- All `warn` severity, all detection-only.
1281
- Needed no new web fetch — every term was already confirmed against a live page by five existing verification reports; see `packages/recheck/presets/inclusive-language/PROVENANCE.md` for the report → row → term table and every single-guide term left out on purpose.
1282
- Layer it onto either flagship or onto `recheck/prose`: `extends: [recheck/google, recheck/inclusive-language]`.
1283
- **Because it's built as an intersection, every one of its 11 rules is already shipped by at least one flagship's own preset**
1284
- (measured: 7 of 11 duplicate a `google/*` finding on the same span when stacked onto `recheck/google` alone, 6 of 11 duplicate a `microsoft/*` finding when stacked onto `recheck/microsoft` alone — see `packages/recheck/presets/inclusive-language/PROVENANCE.md`'s "Duplicate-finding audit").
1285
- Its full, zero-duplicate value is realized standalone, with `recheck/prose`, or on a project using neither flagship; stacked onto exactly one flagship it still fills that flagship's own gaps, but expect a majority of its findings to be reported twice.
1286
- - **`recheck/plain-language`** — composable, derived from the *live* US federal plain-language guidance (`digital.gov/guides/plain-language`; public domain, no attribution constraint).
1287
- Smaller than a first read of the old `plainlanguage.gov` site would suggest:
1288
- that site is now dead and redirects to a much thinner overview, so there's no sentence-length or readability-`metric` rule (`metric` stays a documented [opt-in](#opt-in-prose-assertions), unchanged) —
1289
- only paragraph length (the one family with real, quotable numbers), filler/wordy phrases, complex-word substitutes, redundant pairs, double negatives, and jargon-to-plain examples.
1290
- **`shall` is never flagged** — it's a defined RFC 2119 normative keyword used throughout specs and API docs, exactly what Recheck lints; `implement` and `command` carry the identical technical-sense collision and are excluded the same way.
1291
- All `warn`/`error` (paragraph-length ceiling only) severity, all detection-only.
1292
- `in order to` and `utilize`/`utilization` are deliberately NOT shipped despite being live, verbatim guide content — both flagships already ship the identical pair, so keeping them here would only ever produce a duplicate finding, never new coverage (measured: this cut duplicate findings on the same fixture from 6 to 3 against `recheck/google`, and from 5 to 3 against `recheck/microsoft`).
1293
- The 3 that remain are an accepted paragraph-length overlap with `recheck/microsoft` (two independently-sourced numbers, not the same fact restated) and a coincidental substring collision with `use-contractions`, not content duplication.
1294
- See `packages/recheck/presets/plain-language/PROVENANCE.md` for every rule's source quote, every family considered and left out, and the full duplicate-finding audit.
1295
-
1296
- - **`recheck/technical-english`** — composable, an original rule set that helps writers follow the principles of ASD-STE100 Simplified Technical English: sentence length (max 25 words, the descriptive-text bound; tighten to 20 for procedures), paragraph length (max 6 sentences), and a passive-voice heuristic at `info`.
1297
- ASD-STE100 Simplified Technical English is a Copyright and a Trade Mark of ASD, Brussels, Belgium.
1298
- This preset is an independent work that ASD and the STEMG do not review, approve, certify, or endorse, and it reproduces no part of the standard — not its text and not its dictionary (compose with `recheck/plain-language` for word-choice checking).
1299
- See `packages/recheck/presets/technical-english/PROVENANCE.md` for the STEMG correspondence and every deliberate omission.
1300
-
1301
- **All five of the presets above — `recheck/google`, `recheck/microsoft`,
1302
- `recheck/inclusive-language`, `recheck/plain-language`, and `recheck/technical-english` — are
1303
- detection-only by design, not by omission: no rule in any of them
1304
- auto-fixes, ever.**
1305
- This is enforced structurally (`fix: false` on every rule, set once by a
1306
- loop at the end of each preset's builder function) and guarded by a test
1307
- that reads the live preset object and fails if a future rule change ever
1308
- makes one fixable again — see `preset-google.test.ts`'s and
1309
- `preset-microsoft.test.ts`'s "is detection-only" describe blocks, and
1310
- `preset-composition.test.ts`'s list-driven version covering all four.
1311
- `recheck/google` and `recheck/microsoft` once had fixable rules.
1312
- Every
1313
- attempt to define a safe subset of them found the fixes corrupting
1314
- genuinely correct prose, in every category previously believed safe:
1315
- spelling (Hemingway's correctly spelled *A Moveable Feast* → "A Movable
1316
- Feast"), hyphenation ("read only the introduction" → "read-only the
1317
- introduction"), and at least one outright inversion of meaning ("No SQL is
1318
- used here" → "NoSQL is used here").
1319
- A rule's *category* does not predict
1320
- fix safety at this scale: a style guide states intent while
1321
- `swap`/`consistency`/`pattern` match tokens, and narrowing which
1322
- categories count as "safe" doesn't close that gap.
1323
- Detection is
1324
- unaffected — every rule still runs and reports, and you apply the fix
1325
- yourself with the judgment style guidance has always required.
1326
- This is the
1327
- same reason Vale, the tool these presets replace, never shipped this class
1328
- of bug.
1329
- See the "Detection-only" sections of
1330
- `packages/recheck/presets/google/PROVENANCE.md` and
1331
- `packages/recheck/presets/microsoft/PROVENANCE.md` for the full history.
1332
-
1333
- The heading rule uses **sentence case** *(changed from AP title case by product decision 2026-07-29: Redocly's own guide, Google, and Microsoft all mandate sentence case)*.
1334
- Two details make that default safe out of the box:
1335
-
1336
- - **The [built-in technical proper-noun vocabulary](#built-in-technical-proper-noun-vocabulary)** — `TECHNICAL_PROPER_NOUNS` — is unioned into `exceptions` by `capitalization` itself (default `builtinVocabulary: true`), so this preset doesn't ship its own copy: `$sentence` still won't flag `OpenAPI`, `GitHub`, `macOS`, and the rest of that list out of the box.
1337
- It's a common-vocabulary floor, not a full brand list — extend it with your own product/company names via this rule's own `exceptions`, which **compose** with the built-ins rather than replacing them (unlike a preset-shipped list, which a same-key override would have replaced entirely).
1338
- - **`fix: false`** — a sentence-case auto-fix would lowercase any proper noun the built-ins and your own `exceptions` don't cover, silently damaging content.
1339
- Set `fix: true` on your own `recheck/capitalization` key (or drop the key) once your exceptions list covers your vocabulary.
5
+ Use it through Redocly CLI.
6
+ Configure it in the `recheck` block of `redocly.yaml`, and name presets in the root `extends`:
1340
7
 
1341
8
  ```yaml
1342
9
  extends:
1343
- - recheck/markdown-relaxed
1344
-
1345
- # Your own overrides win over the preset:
1346
- recheck/line-length:
1347
- severity: warn
1348
- assertions:
1349
- line-length:
1350
- lineLength: 120
1351
- ```
1352
-
1353
- Multiple presets can be listed; later presets in the list override earlier ones for the same rule key, before your own top-level rule keys are merged in last.
1354
-
1355
- ### Tune a preset
1356
-
1357
- Adopting a whole style-guide preset doesn't mean accepting every rule at its shipped severity.
1358
- Because your own config's rule keys always win over a preset's (see above), you can turn individual rules off, downgrade them, or silence single occurrences — all verified against a live build, not just read from source:
1359
-
1360
- ```yaml
1361
- extends: [recheck/markdown, recheck/microsoft]
1362
-
1363
- microsoft/az-navigation:
1364
- severity: off # turn a rule off entirely
1365
-
1366
- microsoft/heading-sentence-case:
1367
- severity: warn # downgrade an error to a warning (this rule ships at error)
1368
- ```
1369
-
1370
- Or silence one occurrence instead of the whole rule, with an inline directive (works on any rule, from any preset — see [Inline Directives](#inline-directives) above):
1371
-
1372
- ```markdown
1373
- <!-- recheck-disable-next-line microsoft/az-navigation -->
1374
- Click the hot link to continue.
1375
-
1376
- <!-- recheck-disable microsoft/az-navigation -->
1377
- ...several occurrences here are all silenced...
1378
- <!-- recheck-enable microsoft/az-navigation -->
1379
-
1380
- <!-- recheck-disable-file -->
1381
- ```
1382
-
1383
- **The sharp edge:** merging a user override on top of a preset rule happens per *assertion id*, not per option inside it.
1384
- Setting a partial override on a bundled `swap` or `pattern` rule doesn't just change the one option you named — it **replaces that assertion object entirely**, silently discarding everything else it carried.
1385
- For example:
1386
-
1387
- ```yaml
1388
- microsoft/spelling-hyphenation:
1389
- assertions:
1390
- swap:
1391
- ignoreCase: false
10
+ - recommended
11
+ - recheck/markdown
12
+ recheck:
13
+ rules:
14
+ recheck/line-length: off
1392
15
  ```
1393
16
 
1394
- drops the preset's whole `pairs` map along with it, and the config then fails validation outright (verified against this exact rule on a live build):
17
+ Run it with `npx @redocly/cli recheck`.
18
+ Its documentation is the [Markdown and prose linting](../../docs/@v2/recheck/index.md) section and the [recheck command page](../../docs/@v2/commands/recheck.md).
1395
19
 
1396
- ```text
1397
- Rule "microsoft/spelling-hyphenation": swap requires a "pairs" object mapping find -> replace strings
1398
- ```
20
+ Two agent skills for AI coding assistants ship in [`skills/`](./skills): `recheck-lint` runs the command on touched Markdown, and `recheck-config` tunes the `recheck` block.
21
+ Install them with `npx skills add https://redocly.com`, or copy them from `node_modules/@redocly/recheck/skills/` into your project's `.claude/skills/`.
22
+ The repository keeps the same files under [`.claude/skills/`](../../.claude/skills), which is where the website publishes them from, so change both copies together.
1399
23
 
1400
- So today, to reject just one term out of a bundled `swap`/`pattern` rule, your options are: turn the whole rule off, restate its entire `pairs`/`tokens` yourself, or inline-disable each occurrence as shown above.
1401
- Two assertion types already have a real per-term escape hatch that doesn't hit this edge: `capitalization`'s `exceptions` (an array of allowed terms that **composes** with the built-in technical-proper-noun vocabulary and anything else you add, rather than replacing it) and `spelling`'s `ignore`.
1402
- A per-term opt-out for `swap`/`pattern` is a known follow-up, not shipped yet.
24
+ ## Programmatic use
1403
25
 
1404
- ### Example configs
26
+ The package exports the engine for tools that embed it:
1405
27
 
1406
- `packages/recheck/examples/{google,microsoft,inclusive-language,plain-language,technical-english}.yaml` are ready-to-copy configs for the five style presets,
1407
- generated by `pnpm examples:generate` (`packages/recheck/scripts/generate-examples.mjs`) so they can never drift from the preset they document —
1408
- a test (`src/config/__tests__/examples-drift.test.ts`) byte-compares each on-disk file against a fresh render and fails, naming the file, if either the preset or the file's own hand-maintained appendix (`examples/appendices/<name>.appendix.yaml`) changes without regenerating.
28
+ - `@redocly/recheck/presets` exports the presets as a plugin, `recheckPresetsPlugin`.
29
+ Pass it in `plugins` to `loadConfig` or `createConfig` of `@redocly/openapi-core`, and read the merged block from `config.recheck`.
30
+ - `resolveRecheckConfig({ block, configDir })` turns a resolved `recheck` block into normalized rules.
31
+ - `lintFiles` and `lintContent` run those rules over files or strings.
32
+ - `runLint`, `generateBaseline`, `runReadability`, and `generateMarkdocSchema` are the actions the CLI command calls.
33
+ Each action returns a result object.
34
+ The CLI prints it.
35
+ `runLint`, `generateBaseline`, and `runReadability` accept one path or a list of paths.
36
+ - `parseMarkdown`, `extractScopes`, and `applyFixesToContent` expose the parser, the scope extractor, and the fixer.
1409
37
 
1410
- Each file has four parts, in this order:
38
+ The package depends on `@redocly/openapi-core` for the `Plugin` type of the presets entry.
1411
39
 
1412
- 1. **An attribution header** — source, license, and sync date, as YAML comments (mirrors that preset's `PROVENANCE.md`).
1413
- 2. **`# What to paste`** — the actual adoption cost: a two-to-four-line `extends` block.
1414
- This is the only part most readers need; everything below it is supporting material, not something to copy.
1415
- 3. **`# How to tune it`** — override patterns verified to work today (turn a rule off, downgrade its severity, inline-disable one occurrence with an HTML comment),
1416
- plus a documented sharp edge: overriding one option on a bundled `swap`/`pattern` rule's `assertions` **replaces that assertion entirely**, silently discarding options like a `pairs` map you didn't restate (merging is per *assertion id*, not per option) —
1417
- restate the whole map, turn the rule off, or use an inline directive instead.
1418
- `capitalization`'s `exceptions` and `spelling`'s `ignore` are the two assertion types that already have a real per-term escape hatch; an equivalent for `swap`/`pattern` is a known follow-up, not shipped yet.
1419
- 4. **`# Full expansion (reference)`** — the preset's entire resolved rule set (alphabetized), so a reader can see exactly what they're adopting without running the tool.
1420
- Every value here is identical to what the `extends` block above already resolves to, so copying this section too is redundant, not broken — it's for reading, not pasting.
40
+ The standalone `recheck` binary and the `recheck.yaml` file are not part of this package.
1421
41
 
1422
- A hand-maintained appendix is appended verbatim after part 4: NOISY candidates the guide states but the preset doesn't enforce (shown as the rule they'd be if shipped, commented out, with a one-line false-positive note each) and a checklist of guide content that needs a human, not a linter (NOT-ENFORCEABLE — active voice, missing-Oxford-comma detection, and similar).
42
+ ## Configuration
1423
43
 
1424
44
  ### Opt-in prose assertions
1425
45
 
1426
- `recheck/prose` (above) intentionally ships only `repetition`, `consistency`, and `capitalization` — a small, broadly-applicable default.
1427
- Three more Vale-parity/native assertions exist (see [Assertion Types](#assertion-types) above for full per-option tables) but are **not shipped in any preset**, because their thresholds, patterns, or dictionaries are inherently project-specific rather than having one right-for-everyone default: `conditional`, `metric`, `spelling`.
1428
- (`length` and `occurrence` used to be entries here; neither is an opt-in any more — [`recheck/google`](#extends-presets) ships `length` directly for the guide's sentence-length limit, and [`recheck/microsoft`](#extends-presets) ships `occurrence` directly for the guide's comma-density rule, so neither one's default bounds are "no one right answer" any more.)
1429
- Add any of the three by copying its rule below into your own config, alongside `extends: [recheck/prose]`:
1430
-
1431
- ```yaml
1432
- extends: [recheck/markdown, recheck/prose]
1433
-
1434
- # conditional: if "TBD" appears, a tracking-issue link must exist somewhere in the file.
1435
- recheck/tbd-needs-tracking-link:
1436
- severity: warn
1437
- message: '"%s" appears but "%s" was never introduced.'
1438
- assertions:
1439
- conditional:
1440
- first: '\bTBD\b'
1441
- second: 'https://github\.com/\S+/issues/\d+'
1442
-
1443
- # metric: flag prose below a Flesch reading-ease floor (higher score = easier to read).
1444
- # Use `formula: word-count`, `sentence-count`, or `reading-time` for a page-size budget instead.
1445
- # The message's four slots are positional: formula, score, min, max (see "Metric Assertions").
1446
- recheck/readability-floor:
1447
- severity: warn
1448
- message: 'Readability (%s) is %s; expected between %s and %s.'
1449
- assertions:
1450
- metric:
1451
- formula: flesch-reading-ease
1452
- min: 30
1453
-
1454
- # spelling: requires the optional `nspell`/`dictionary-en` peers -- see
1455
- # "Spelling Assertions" above for the install command.
1456
- recheck/us-spelling-check:
1457
- severity: warn
1458
- message: 'Unknown word "%s"%s'
1459
- assertions:
1460
- spelling:
1461
- vocab: [Redocly, Reunite]
1462
- ```
46
+ `conditional`, `metric`, and `spelling` have no single right default.
47
+ Add them explicitly under `recheck.rules`.
1463
48
 
1464
- Each snippet's `severity`, `message`, `scope`, and `exceptions` are yours to adjust — see [Rule Types and Assertions](#rule-types-and-assertions) for every option each assertion accepts, and [Inline Directives](#inline-directives) to silence any one of them on a specific line or file with an HTML comment instead of turning it off entirely.
1465
-
1466
- ### Migrate from markdownlint
1467
-
1468
- A markdownlint config maps onto Recheck almost 1:1 — `extends` a preset, then override individual rules by their Recheck name (same short name markdownlint uses, e.g. `line-length` for MD013) under `assertions`:
49
+ `metric` scores the prose of a whole file and reports once when the score is outside `min` and `max`.
50
+ Its `formula` is a readability formula (`flesch-reading-ease`, `flesch-kincaid-grade`, `gunning-fog`, `smog`, `coleman-liau`, `automated-readability`) or a size formula (`word-count`, `sentence-count`, `reading-time`).
51
+ `reading-time` is minutes at `wordsPerMinute`, default 200, rounded to one decimal.
52
+ Size formulas count what a person reads: code blocks, front matter, headings, and Markdoc tags are left out.
53
+ Use one as a page-size budget, such as a word cap on agent skills:
1469
54
 
1470
55
  ```yaml
1471
56
  extends:
1472
57
  - recheck/markdown
1473
-
1474
- recheck/line-length:
1475
- severity: warn
1476
- message: 'Keep lines under %s characters.'
1477
- assertions:
1478
- line-length:
1479
- lineLength: 120
1480
- codeBlocks: false
1481
-
1482
- recheck/ul-style:
1483
- severity: off
1484
- ```
1485
-
1486
- **Renamed legacy assertion ids — old ids are no longer accepted.**
1487
- A handful of ids from Recheck's pre-parity native rules were converged onto their markdownlint-parity replacements.
1488
- These four were removed outright, not kept as aliases: using the old id in a config now fails validation with an `unknown assertion type "<old>"` error, and the config must be updated to the new id:
1489
-
1490
- | Old id (removed) | Use instead |
1491
- | --- | --- |
1492
- | `max-line-length` | `line-length` (MD013) |
1493
- | `bullet-style` | `ul-style` (MD004) |
1494
- | `no-duplicate-headings` | `no-duplicate-heading` (MD024) |
1495
- | `no-broken-fragment-links` | `link-fragments` (MD051) |
1496
-
1497
- Two other ids are **upstream markdownlint's own alternate rule names**, not a Recheck deprecation — these remain permanent, warning-free aliases and require no config change:
1498
-
1499
- | Upstream synonym | Canonical id |
1500
- | --- | --- |
1501
- | `first-line-heading` | `first-line-h1` (MD041) |
1502
- | `single-title` | `single-h1` (MD025) |
1503
-
1504
- **Two intentional behavior changes** vs. plain markdownlint defaults, both on rules that predate the parity port and kept their exact ids:
1505
-
1506
- - **`no-trailing-spaces` (MD009)** now exempts lines with *exactly 2* trailing spaces by default (a markdown hard line break), instead of flagging all trailing whitespace.
1507
- Set `strict: true` to restore the old flag-everything behavior (matches markdownlint's default).
1508
- - **`no-hard-tabs` (MD010)**'s `spacesPerTab` option now defaults to `1` (matching markdownlint's own upstream default) — Recheck's earlier, pre-parity native rule had defaulted this to `2`.
1509
- If you were relying on that old default, set `spacesPerTab: 2` explicitly.
1510
-
1511
- Token-rule options aren't individually schema-validated, so an option name a rule doesn't recognize is silently ignored — it has no effect, and produces no warning or error.
1512
-
1513
- ### Cross-file link validation
1514
-
1515
- `link-fragments` accepts `crossFile: true` to validate links across files, replacing external link checkers such as `mlc` for repo-internal links:
1516
-
1517
- - A relative link or image target must exist on disk (`[x](./missing.md)` flags).
1518
- - A `file.md#anchor` fragment must exist in the target file's headings and anchors.
1519
- - Extensionless links resolve the way the Realm router does: `./page` tries `page.md`, and a directory link reads its `index.md`.
1520
- - Site-root absolute paths (`/x/y`) resolve against the `rootDir` option when set, and are skipped without it.
1521
- - `ignoredTargets` skips destinations by glob (`['/gateways/**']`) — for routes a renderer generates from data, with no file on disk.
1522
- - Links to `<details>` sections resolve: a `<details>` without an explicit `id` gets one derived from its `<summary>` text, the same way the theme generates them in the browser.
1523
- - Markdoc tags in headings (`## Payments {% badge /%}`) do not change the heading's anchor.
1524
- In a monorepo with several docs projects, give `rootDir` a map from source-directory prefix to that directory's site root; the longest matching prefix wins, and files under no prefix keep the skip.
1525
- Paths are relative to the working directory.
1526
- - External URLs and `mailto:` are skipped.
1527
-
1528
- Each target file is read once per run and cached by modification time.
1529
- The option is off by default; in-document fragment checking is unchanged.
1530
-
1531
- ### Known differences from markdownlint
1532
-
1533
- - **Inline HTML-comment disable directives are not supported**, for example:
1534
-
1535
- ```html
1536
- <!-- markdownlint&#45;disable -->
1537
- ```
1538
-
1539
- (dash HTML-escaped above so this very README doesn't trip markdownlint's own directive scanner — markdownlint recognizes these directives even inside fenced code, so the literal syntax can't appear here unescaped).
1540
- Markdownlint's HTML-comment-based per-line/per-region rule toggles (`markdownlint-disable`, `markdownlint-disable-next-line`, `markdownlint-enable`, etc.) are a distinct engine feature, not a rule port, and Recheck doesn't parse them today.
1541
- Use config-level `exceptions` (file/line patterns) or `excludes`/`appliesTo` to achieve the same effect.
1542
- Native support is a possible future addition; no decision has been made yet.
1543
-
1544
- ### Parity with markdownlint
1545
-
1546
- The 53 ported rules are checked against upstream markdownlint by a differential harness (`pnpm parity`, `benchmarks/parity/run-parity.mjs`): the harness lints the same real-world document set with both Recheck (via a config translated from markdownlint's option surface) and markdownlint itself, then set-diffs the findings.
1547
- As of this writing it reports **zero unexplained differences** across:
1548
-
1549
- - `mdn-content` (MDN Web Docs, ~14.5k files)
1550
- - `electron` (Electron's docs + repo markdown)
1551
- - `monorepo-docs` (this monorepo's own `docs/` tree)
1552
-
1553
- on both the `default` profile (full `recheck/markdown` preset vs. markdownlint `{ default: true }`) and a `rebilly` profile (a real third-party `.markdownlint.yaml` translated to Recheck config).
1554
- A small, explicitly documented allowlist (`benchmarks/parity/allowlist.json`) covers the one known permanent engine-surface gap — inline `markdownlint-disable` directives (see [Known differences](#known-differences-from-markdownlint) above) — scoped to the exact rules it can affect (MD010, MD011, MD033, MD059).
1555
- Everything else matches exactly.
1556
-
1557
- **`pnpm parity` requires `--corpus`** — running it bare exits `2` with a usage error (`Usage: node benchmarks/parity/run-parity.mjs --corpus <name> [--profile default|rebilly] [--rules MD001,MD013]`) rather than running against a default document set.
1558
- Always pass a document-set name, e.g. `pnpm parity --corpus monorepo-docs --profile default`.
1559
-
1560
- ### Front matter validation
1561
-
1562
- The `front-matter` token rule validates front matter against JSON Schema, using `@redocly/ajv` — the same validator Redocly CLI uses.
1563
- Map file patterns to schemas; the first matching mapping wins, and a file that matches no mapping is not checked.
1564
-
1565
- ```yaml
1566
- recheck/front-matter:
1567
- severity: error
1568
- message: 'Front matter: %s'
1569
- assertions:
1570
- front-matter:
1571
- schemas:
1572
- - files: ['.changeset/**']
1573
- schema:
1574
- type: object
1575
- patternProperties:
1576
- '^@redocly/': { enum: [major, minor, patch] }
1577
- additionalProperties: false
1578
- - files: ['docs/**']
1579
- schemaFile: schemas/docs-front-matter.yaml
1580
- ```
1581
-
1582
- - `schema` is an inline JSON Schema object, or the name of a built-in schema (see below); `schemaFile` loads one from a YAML or JSON file, relative to the working directory.
1583
- - A file with no front matter validates as an empty object, so the schema's `required` list decides whether front matter is mandatory.
1584
- - Findings point at the line of the offending top-level key; front matter that is not valid YAML is one finding at the block start.
1585
-
1586
- #### Built-in schema: Realm page front matter
1587
-
1588
- `schema: realm` validates the front matter options a Realm page accepts, so a project gets the check without vendoring a copy that goes stale:
1589
-
1590
- ```yaml
1591
- - files: ['docs/**']
1592
- schema: realm
1593
- strict: true
1594
- ```
1595
-
1596
- It covers the front-matter-only options (`excludeFromSearch`, `sidebar`, `slug`, `template`, `navigation`, `keywords`), the options that override `redocly.yaml` (`banner`, `breadcrumbs`, `codeSnippet`, `colorMode`, `feedback`, `footer`, `markdown`, `metadata`, `navbar`, `navigation`, `rbac`, `redirects`, `search`, `seo`, `versionPicker`), and `title`/`description`.
1597
-
1598
- The schema checks the **type** of each known key, not the inner shape of the option objects.
1599
- `seo`, `markdown`, and their siblings are whole configuration blocks that Realm evolves independently, so encoding their structure here would drift and start rejecting valid pages.
1600
- Type checking still catches what actually goes wrong: a misspelled key (with `strict`), and a value of the wrong kind (`excludeFromSearch: "true"`).
1601
-
1602
- `strict: true` adds `additionalProperties: false`, which turns a misspelled option into a finding.
1603
- It is off by default because pages carry project data that Markdoc templates read back through `$frontmatter.<key>` — Redocly's own docs use `products` and `plans` this way on 268 pages.
1604
- To keep `strict` and allow your own keys, copy the built-in as a starting point and add them.
1605
-
1606
- ## GitHub Actions Integration
1607
-
1608
- Recheck provides seamless GitHub Actions integration for automated content quality checking on pull requests.
1609
-
1610
- ### Output Format: `github-actions`
1611
-
1612
- Use the `--output github-actions` format to generate inline file annotations that appear directly on pull request files:
1613
-
1614
- ```bash
1615
- # Basic GitHub Actions output
1616
- node dist/cli.js docs --output github-actions
1617
-
1618
- # With annotation limits (recommended for PR workflows)
1619
- node dist/cli.js docs --output github-actions --annotations-limit 20
1620
- ```
1621
-
1622
- ### Annotation Output
1623
-
1624
- The GitHub Actions format produces annotations that GitHub automatically displays as inline comments:
1625
-
1626
- ```text
1627
- ::error title=recheck/no-trailing-spaces,file=docs/guide.md,line=42,col=15,endColumn=18::Trailing spaces
1628
- ::warning title=recheck/ul-style,file=docs/api.md,line=23,col=1,endColumn=2::Unordered list style
1629
- ```
1630
-
1631
- ### GitHub Actions Limits
1632
-
1633
- GitHub Actions has strict limits on annotations:
1634
- - **10 error** and **10 warning** annotations per step
1635
- - **50 total** annotations per job
1636
-
1637
- **Recommendation**: Use `--annotations-limit 20` (the default) to stay well within these limits while prioritizing the most critical issues.
1638
-
1639
- ### Workflow Example
1640
-
1641
- ```yaml
1642
- - name: Run recheck with inline annotations
1643
- run: |
1644
- node packages/recheck/dist/cli.js docs \
1645
- --config recheck.yaml \
1646
- --output github-actions \
1647
- --changed-only < changed-files.txt
1648
- ```
1649
-
1650
- This creates both inline file annotations AND summary comments when combined with the PR comment workflow.
1651
-
1652
- ## Performance
1653
-
1654
- Recheck's file-first, parse-once architecture is benchmarked directly against `markdownlint`'s own library API (`benchmarks/run-markdownlint.mjs`) on the same corpora, using `pnpm bench` (see `benchmarks/bench.mjs`):
1655
-
1656
- - **Phase 1** (10 native scope rules vs. markdownlint's default rules, `monorepo-docs` document set, 953 files): recheck **0.91×** markdownlint's median time (3219ms vs. 3521ms) — see `benchmarks/results/phase1-ast-core.json` / `baseline-markdownlint.json`.
1657
- - **Phase 2** (all 53 markdownlint-parity rules via `extends: [recheck/markdown]` vs. markdownlint's `{ default: true }`, equivalent rule sets):
1658
- - `monorepo-docs` (954 files): recheck 4468ms vs. markdownlint 3755ms — **1.19×**.
1659
- - `mdn-content` (14,515 files, a real-world OSS document set): recheck 41839ms vs. markdownlint 34548ms — **1.21×**.
1660
-
1661
- Both Phase 2 numbers sit comfortably inside the ±40% parity gate (recheck's median must fall within `[0.6×, 1.4×]` of markdownlint's on an equivalent rule set) enforced by:
1662
-
1663
- ```bash
1664
- pnpm bench --subject benchmarks/run-recheck-mdl-preset.mjs --corpus monorepo-docs --record <label>
1665
- pnpm bench --subject benchmarks/run-markdownlint.mjs --corpus monorepo-docs --record <label>
1666
- ```
1667
-
1668
- Recheck trades a bit of the Phase 1 constant-factor lead for full rule-count parity (53 rules vs. 10) — still within budget, and the shared AST-parse-once architecture means adding prose/style rules on top costs little extra, since markdown structure parsing is already paid for.
1669
-
1670
- - **Phase 3** (prose profile — the standard Phase 2 rule set vs. that same set plus the Vale-parity prose additions, `monorepo-docs` document set, the same set on both sides):
1671
- - Standard profile (`recheck-mdl-preset.yaml`, `extends: [recheck/markdown]`, 53 rules, `run-recheck-mdl-preset.mjs`, 965 files): **4072ms** median — **-8.9% vs the Phase 2 recording** (`phase2-parity-recheck`, 4468ms, 954 files),
1672
- i.e. no regression from the Phase 3 rule-registry additions, since the standard profile doesn't exercise any of them (the small speedup is run-to-run variance plus the document-set size difference, not an optimization claim).
1673
- - Prose profile (`recheck-prose-bench.yaml`, `extends: [recheck/markdown, recheck/prose]` plus one opt-in `occurrence` rule and one opt-in `conditional` rule, 58 rules, `run-recheck-prose.mjs`, same 965 files): **4547ms** median — **+11.7%** vs. the standard profile above, for the five added prose/scope rules (`repetition`, `consistency`, `capitalization`, and the two opt-ins).
1674
- Comfortably under the "investigate if >2x standard" threshold; no pathological per-rule cost found.
1675
- There is no hard pass/fail gate for this profile (new profile, first recording) — these numbers establish its baseline.
1676
- - Measured 2026-07-27 (local time; the result JSONs record the UTC date `2026-07-28`, so the file dates and this measurement date differ by design, not by error) at commit `27a8b6feb10` (immediately prior to the commit that added this benchmark profile), on an Apple M2 Max / Darwin 24.6.0 / Node v23.7.0 machine;
1677
- single 3-run session, not a statistically rigorous multi-session average — treat the deltas as directional, not precise.
1678
- `pnpm bench --subject <script> --corpus monorepo-docs --runs 3 --record <label>` (median of 3 timed runs after 1 warm-up); recorded to `benchmarks/results/phase3-prose-standard.json` / `phase3-prose-profile.json`.
1679
- A `--corpus self` (2-file) smoke pair recorded to `phase3-prose-standard-self.json` / `phase3-prose-profile-self.json` proves the harness/subject-script mechanics end-to-end but isn't large enough to be a meaningful timing signal on its own.
1680
-
1681
- - **Phase 4** (refreshed standard/prose figures plus the new `recheck/google` profile, `monorepo-docs` document set, the same set across all three, 968 files — the document set grew by 3 files since the Phase 3 recording):
1682
- - Standard profile (same config as Phase 2/3, `recheck-mdl-preset.yaml`, 53 rules): **4350ms** median (runs 4341/4350/4369ms — 28ms spread, 0.6% of median: a tight, trustworthy measurement).
1683
- This refreshes — and for current comparisons supersedes — Phase 3's `4072ms`/965-file recording; the ~7% difference is within normal session-to-session noise (different process/cache/scheduler state), not a regression, and there is still no rule-registry change that would affect this profile.
1684
- - Prose profile (same config as Phase 3, `recheck-prose-bench.yaml`, 58 rules): **4801ms** median (runs 4599/4801/4840ms — 241ms spread, 5.0% of median) — **+10.4%** vs. the refreshed standard profile above.
1685
- This is the requested refresh of the Phase 3 prose figures, which the `capitalization` default's AP-title-case → sentence-case change (2026-07-29, see [`extends` presets](#extends-presets) below) made marginally stale, since this profile's `capitalization` rule is exactly what that change touched.
1686
- The new delta (+10.4%) is close to Phase 3's original (+11.7%); given the 5.0% run-to-run spread observed here, treat both numbers as directionally consistent, not as proof of a precise change in cost — same posture Phase 3 itself took.
1687
- - **`recheck/google` profile** (new — `recheck-google-bench.yaml`, `extends: [recheck/markdown, recheck/google]`, 152 rules total: the same 53-rule structural set plus all 99 rules `recheck/google` ships, `run-recheck-google.mjs`, same 968 files): **11792ms** median (runs 11628/11792/12164ms — 536ms spread, 4.5% of median).
1688
- - **This profile is substantially more expensive than either profile above: roughly 2.7× (~+171%) the standard profile's median time**, a materially different result from the ~1.1–1.2× range Phase 2/3 established for the markdownlint-parity and Vale-parity workloads.
1689
- This is recorded as a **new baseline on its own terms, not a regression against the standard-profile's ±40% parity gate** — that gate applies only to `recheck/markdown` vs. markdownlint's equivalent rule set (Phase 2, unaffected by this preset's addition) and was never meant to bound a ~99-rule prose-preset workload layered on top of it.
1690
- The number is reported as measured, without tuning the preset to improve it.
1691
- - The ~171% delta is far larger than the ≤5% run-to-run spread measured on all three profiles this session, so the *direction and rough magnitude* of "the Google preset costs several times more than the structural set alone" is trustworthy; the precise "171%" is not — this is a single 3-run-median session on one developer machine, not a statistically rigorous benchmark.
1692
- Read it as "roughly 2.5–3×," not as a number with two decimal digits of meaning.
1693
- - **What this means for adopters**: turning on `extends: [recheck/google]` roughly triples per-run lint time on a docs-sized document set (968 files: ~4.3s → ~11.8s).
1694
- That is a real, user-facing cost, stated here so it is visible before adoption rather than discovered later in CI — projects sensitive to CI duration should budget for it explicitly (e.g., a separate, non-blocking job, or a scheduled run) rather than assuming `recheck/google` is free to layer on top of `recheck/markdown`.
1695
- - Measured 2026-07-30 at commit `7c50caaaf05` (immediately prior to the commit that adds this benchmark profile), on the same Apple M2 Max / Darwin 24.6.0 / Node v23.7.0 machine as Phase 1–3; single 3-run session per profile (1 warm-up + 3 timed runs), same caveats as Phase 3 — treat deltas as directional, not precise.
1696
- `pnpm bench --subject <script> --corpus monorepo-docs --runs 3 --record <label>`; recorded to `benchmarks/results/phase4-google-standard.json` / `phase4-google-prose-refresh.json` / `phase4-google-profile.json`.
1697
-
1698
- - **Markdoc parse cost** (`parseMarkdown` in isolation — no rules, no config validation — flag off vs. flag on, `monorepo-docs` document set, 981 files, `run-recheck-parse.mjs` / `run-recheck-parse-markdoc.mjs`): flag off **2825ms** median (runs 2784/2825/2966ms) vs. flag on **2896ms** median (runs 2864/2896/3101ms) — **+2.5%**.
1699
- That gap is smaller than the ~240–280ms run-to-run spread on either side, so read it as "no measurable parse-time cost from turning `markdoc: true` on" rather than a precise 2.5% overhead; the number is reported as measured.
1700
- This is its own baseline row — there is no earlier "parse only, flag off" recording to compare against — and it is deliberately not gated against the ±40% markdownlint-parity budget above, which covers the 53-rule structural comparison and never sets this flag.
1701
- Measured 2026-08-02 at commit `db8df4378ff`, on the same Apple M2 Max / Darwin 24.6.0 / Node v23.7.0 machine as the phases above; single 3-run session per side.
1702
- `pnpm bench --subject benchmarks/run-recheck-parse.mjs --corpus monorepo-docs --runs 3 --record <label>` (and the `-markdoc` sibling script for the flag-on side); recorded to `benchmarks/results/markdoc-parse-cost-flag-off.json` / `markdoc-parse-cost-flag-on.json`.
1703
- - ✅ **Scalable**: File-first architecture with rule indexing optimizes for large repositories; the same micromark AST backs both markdown-structure rules and prose-scope rules, so combining both rule families costs one parse, not two.
1704
-
1705
- ## Dependencies
1706
-
1707
- ### Runtime Dependencies
1708
- - `@redocly/ajv` + `ajv-formats` - JSON Schema validation
1709
- - `js-yaml` - YAML configuration parsing
1710
- - `yargs` - CLI interface
1711
- - `colorette` - Terminal colors
1712
- - `picomatch` - File pattern matching
1713
- - `micromark` + `micromark-extension-directive`, `micromark-extension-frontmatter`, `micromark-extension-gfm-autolink-literal`, `micromark-extension-gfm-footnote`, `micromark-extension-gfm-table`, `micromark-extension-math` - the markdown parser (and its GFM/frontmatter/directive/math extensions) backing the shared token tree that both markdown-structure and prose/scope rules run against
1714
- - `string-width` - measures the display width of strings containing wide/ambiguous-width or ANSI-styled characters, for CLI table output alignment
1715
-
1716
- ### Optional Peer Dependencies
1717
- - `nspell` + `dictionary-en` - Hunspell-compatible spell checker (and its bundled English dictionary) backing the `spelling` assertion (see [Spelling Assertions](#spelling-assertions-spelling) above).
1718
- **Not installed by installing `@redocly/recheck`** — both are declared `optional: true` in `peerDependenciesMeta`, loaded lazily via dynamic `import()` only when a config actually enables `spelling`.
1719
- Run `npm i nspell dictionary-en` to enable it (or just `npm i nspell` if every `spelling` rule supplies its own `dictionary` path).
1720
-
1721
- ### Development Dependencies
1722
- - `vitest` - Modern testing framework
1723
- - `typescript` - Type checking and compilation
1724
- - `markdownlint` - upstream reference implementation, used by the differential parity harness (`pnpm parity`) and its own smoke tests, not by Recheck itself at runtime
1725
- - `nspell` + `dictionary-en` - pinned here too (see Optional Peer Dependencies above) so this package's own test suite can exercise the real speller against real dictionary data
1726
- - `@types/*` - TypeScript definitions
1727
-
1728
- ## Example Output
1729
-
1730
- ### Standard Run
1731
- ```text
1732
- 🏃 Running recheck on: docs/
1733
- ✅ Configuration loaded successfully!
1734
- Config file: recheck.yaml
1735
- Loaded 8 rule(s)
1736
- Disabled 1 rule(s) (severity: off)
1737
-
1738
- 🔧 Running 8 rule(s)...
1739
- Found 311 markdown file(s)
1740
- Checking rule: us-spelling...
1741
- Checking rule: no-gerund-headings...
1742
- Checking rule: oxford-comma...
1743
- Checking rule: no-trailing-spaces...
1744
- Checking rule: ul-style...
1745
- Checking rule: semantic-line-breaks...
1746
- Checking rule: no-hard-tabs...
1747
- Checking rule: no-duplicate-heading...
1748
-
1749
- 📋 Found 1086 issue(s):
1750
-
1751
- us-spelling README.md:68:5 Use the US spelling "color" instead of British "colour".
1752
- no-trailing-spaces README.md:15:42 Trailing spaces
1753
- ul-style docs/guide.md:22:1 Unordered list style
1754
-
1755
- 65 error(s)
1756
- 1021 warning(s)
1757
-
1758
- ❌ Found 65 error(s). Exiting with code 1.
1759
- Completed in 156ms
1760
- ```
1761
-
1762
- ### JSON Output
1763
- ```json
1764
- {
1765
- "summary": {
1766
- "filesScanned": 311,
1767
- "totalIssues": 1086,
1768
- "breakdown": {
1769
- "recheck/no-trailing-spaces": {
1770
- "errors": 65,
1771
- "warnings": 0,
1772
- "info": 0,
1773
- "total": 65
1774
- },
1775
- "recheck/ul-style": {
1776
- "errors": 1021,
1777
- "warnings": 0,
1778
- "info": 0,
1779
- "total": 1021
1780
- }
1781
- }
1782
- },
1783
- "issues": [
1784
- {
1785
- "file": "../../docs/realm/branding/index.md",
1786
- "line": 13,
1787
- "column": 22,
1788
- "text": "Use the brand guidelines when applying Redocly brand",
1789
- "match": " ",
1790
- "ruleName": "recheck/no-trailing-spaces",
1791
- "severity": "error",
1792
- "message": "Trailing spaces"
1793
- }
1794
- ]
1795
- }
1796
- ```
1797
-
1798
- ## What's Working Now
1799
-
1800
- - ✅ **Modern Architecture**: File-first processing — each file is parsed once into a micromark AST, then segmented into scopes for rule application
1801
- - ✅ **Vale Compatibility**: Scope notation compatible with Vale linter
1802
- - ✅ **Swap Rules**: Find and replace text patterns with word boundaries and case sensitivity
1803
- - ✅ **Pattern Rules**: Regex matching with precise scope filtering
1804
- - ✅ **Built-in Assertions**: `swap` and `pattern` general-purpose assertions, a small set of native prose assertions, and 53 markdownlint-parity rules — all fully implemented with comprehensive tests
1805
- - ✅ **File Discovery**: Recursive markdown file finding with common directory exclusions
1806
- - ✅ **Multiple Output Formats**: Human-readable table, structured JSON, SARIF, and GitHub Actions
1807
- - ✅ **Severity Filtering**: Show only errors, warnings, or all issues
1808
- - ✅ **Exit Codes**: Non-zero exit when errors found (perfect for CI)
1809
- - ✅ **Exception Handling**: Skip lines and files that match exception patterns
1810
- - ✅ **Auto-Fix**: Safe automatic correction for appropriate rules
1811
- - ✅ **Production Scale**: Successfully handles large documentation repositories
1812
- - ✅ **Comprehensive Testing**: Full Vitest test suite covering the parser, scope extractor, config validation, rules, and CLI
1813
-
1814
-
1815
- ### Key Design Principles
1816
-
1817
- - **File-First Processing**: Iterate by files, then by semantic scopes within files
1818
- - **Scope-Based Rules**: Apply rules only to relevant content scopes
1819
- - **Type Safety**: Full TypeScript coverage with strict typing
1820
- - **AST-Based Parsing**: A micromark token tree per file backs scope segmentation, replacing regex-based scope parsing
1821
- - **Modular Rules**: Each built-in rule in its own file with dedicated tests
1822
- - **Safe Auto-Fix**: Granular control over which rules can auto-fix
1823
- - **Vale Compatibility**: Scope notation compatible with existing Vale configurations
1824
-
1825
- ## File Targeting
1826
-
1827
- Rules can target specific files using path patterns with `appliesTo` and `excludes`:
1828
-
1829
- ### Apply Rules to Specific Files
1830
-
1831
- ```yaml
1832
- recheck/config-docs-only:
1833
- severity: error
1834
- message: "Config docs must follow specific patterns"
1835
- appliesTo:
1836
- - "docs/config/**" # All files in docs/config directory
1837
- - "**/api/*.md" # All .md files in any api directory
1838
- - "*.config.md" # Files ending with .config.md
1839
- assertions:
1840
- pattern:
1841
- tokens: ["TODO"]
1842
-
1843
- recheck/api-standards:
1844
- severity: warn
1845
- message: "API docs need review"
1846
- appliesTo:
1847
- - "docs/api/**" # Target API documentation
1848
- - "**/endpoints/*.md" # Target endpoint documentation
1849
- assertions:
1850
- pattern:
1851
- tokens: ["DRAFT", "TBD"]
1852
- ```
1853
-
1854
- ### Exclude Files from Rules
1855
-
1856
- ```yaml
1857
- recheck/no-todos:
1858
- severity: error
1859
- message: "No TODOs allowed"
1860
- excludes:
1861
- - "docs/drafts/**" # Exclude all draft documents
1862
- - "**/temp*.md" # Exclude temporary files
1863
- - "README.md" # Exclude specific file
1864
- assertions:
1865
- pattern:
1866
- tokens: ["TODO"]
1867
- ```
1868
-
1869
- ### Path Pattern Support
1870
-
1871
- File targeting supports multiple pattern types:
1872
-
1873
- - **Full path patterns**: `docs/config/**`, `src/components/*.md`
1874
- - **Recursive patterns**: `**/api/*.md`, `**/README.md`
1875
- - **Basename patterns**: `*.config.md`, `temp*.md` (backward compatible)
1876
- - **Exact matches**: `README.md`, `docs/guide.md`
1877
-
1878
- ### Pattern Matching Strategy
1879
-
1880
- The enhanced pattern matching checks patterns against:
1881
-
1882
- 1. **Filename** (`config.md`) - for simple patterns
1883
- 2. **Relative path** (`docs/config/settings.md`) - for full path patterns
1884
- 3. **Path segments** (`config/settings.md`) - for partial path patterns
1885
-
1886
- This allows flexible targeting while maintaining backward compatibility.
1887
-
1888
- ## Contribute
1889
-
1890
- Want to add new assertions or improve existing ones?
1891
- Check out our **[Contributing Guide](src/rules/CONTRIBUTING.md)** for:
1892
-
1893
- - 🏗️ **Architecture overview** - How parse-once file processing and centralized scope filtering work
1894
- - 📝 **Step-by-step guide** - Create new assertions following best practices
1895
- - 🧪 **Testing guidelines** - Comprehensive test coverage examples
1896
- - ✅ **Code standards** - Follow our established conventions
1897
- - 🚀 **Quick examples** - Get started with working code templates
1898
-
1899
- The guide covers our modern architecture where the **runner handles parsing and scope filtering automatically**.
1900
- An assertion's `execute`/`fix` functions can focus on their core logic instead of re-implementing segment selection or file I/O.
1901
-
1902
- ## Future Enhancements
1903
-
1904
- 1. **Plugin System**: Custom rule loading from external files/packages
1905
- 2. **Configuration Inheritance**: Config file discovery and inheritance
1906
- 3. **Watch Mode**: Real-time linting as files change
1907
- 4. **IDE Integration**: Language server protocol support
1908
- 5. **Additional Scopes**: Support for more Vale-compatible scopes
58
+ recheck:
59
+ rules:
60
+ recheck/tbd-needs-tracking-link:
61
+ severity: warn
62
+ message: '"%s" appears but "%s" was never introduced.'
63
+ assertions:
64
+ conditional:
65
+ first: '\bTBD\b'
66
+ second: 'https://github\.com/\S+/issues/\d+'
67
+ recheck/readability-floor:
68
+ severity: warn
69
+ message: 'Readability (%s) is %s; expected between %s and %s.'
70
+ assertions:
71
+ metric:
72
+ formula: flesch-reading-ease
73
+ min: 30
74
+ recheck/skill-size-budget:
75
+ severity: error
76
+ message: 'Document %s is %s; expected between %s and %s.'
77
+ appliesTo:
78
+ - '.claude/skills/**/SKILL.md'
79
+ assertions:
80
+ metric:
81
+ formula: word-count
82
+ max: 1500
83
+ recheck/us-spelling-check:
84
+ severity: warn
85
+ message: 'Unknown word "%s"%s'
86
+ assertions:
87
+ spelling:
88
+ vocab: [Redocly, Reunite]
89
+ ```
90
+
91
+ ## How to contribute
92
+
93
+ See the [Recheck engine](../../CONTRIBUTING.md#recheck-engine) section of the root contributing guide.